/*
 * Copyright 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.
 */

#include <binder/MemoryDealer.h>

#include "../../../config/TunerTestingConfigAidlReaderV1_0.h"

#include <aidl/android/hardware/tv/tuner/DataFormat.h>
#include <aidl/android/hardware/tv/tuner/DemuxAlpFilterType.h>
#include <aidl/android/hardware/tv/tuner/DemuxFilterMainType.h>
#include <aidl/android/hardware/tv/tuner/DemuxFilterMonitorEventType.h>
#include <aidl/android/hardware/tv/tuner/DemuxFilterSettings.h>
#include <aidl/android/hardware/tv/tuner/DemuxFilterType.h>
#include <aidl/android/hardware/tv/tuner/DemuxIpAddress.h>
#include <aidl/android/hardware/tv/tuner/DemuxIpFilterSettings.h>
#include <aidl/android/hardware/tv/tuner/DemuxIpFilterType.h>
#include <aidl/android/hardware/tv/tuner/DemuxMmtpFilterType.h>
#include <aidl/android/hardware/tv/tuner/DemuxRecordScIndexType.h>
#include <aidl/android/hardware/tv/tuner/DemuxTsFilterType.h>
#include <aidl/android/hardware/tv/tuner/DvrSettings.h>
#include <aidl/android/hardware/tv/tuner/DvrType.h>
#include <aidl/android/hardware/tv/tuner/FrontendDvbtBandwidth.h>
#include <aidl/android/hardware/tv/tuner/FrontendDvbtCoderate.h>
#include <aidl/android/hardware/tv/tuner/FrontendDvbtConstellation.h>
#include <aidl/android/hardware/tv/tuner/FrontendDvbtGuardInterval.h>
#include <aidl/android/hardware/tv/tuner/FrontendDvbtHierarchy.h>
#include <aidl/android/hardware/tv/tuner/FrontendDvbtSettings.h>
#include <aidl/android/hardware/tv/tuner/FrontendDvbtStandard.h>
#include <aidl/android/hardware/tv/tuner/FrontendDvbtTransmissionMode.h>
#include <aidl/android/hardware/tv/tuner/FrontendSettings.h>
#include <aidl/android/hardware/tv/tuner/FrontendType.h>
#include <aidl/android/hardware/tv/tuner/PlaybackSettings.h>
#include <aidl/android/hardware/tv/tuner/RecordSettings.h>

using namespace std;
using namespace aidl::android::hardware::tv::tuner;
using namespace android::media::tuner::testing::configuration::V1_0;

const int32_t FMQ_SIZE_4M = 0x400000;
const int32_t FMQ_SIZE_16M = 0x1000000;

#define FILTER_MAIN_TYPE_BIT_COUNT 5
#define STATUS_CHECK_INTERVAL_MS 100L

// Hardware configs
static map<string, FrontendConfig> frontendMap;
static map<string, FilterConfig> filterMap;
static map<string, DvrConfig> dvrMap;
static map<string, LnbConfig> lnbMap;
static map<string, TimeFilterConfig> timeFilterMap;
static map<string, vector<uint8_t>> diseqcMsgMap;
static map<string, DescramblerConfig> descramblerMap;

// Hardware and test cases connections
static LiveBroadcastHardwareConnections live;
static ScanHardwareConnections scan;
static DvrPlaybackHardwareConnections playback;
static DvrRecordHardwareConnections record;
static DescramblingHardwareConnections descrambling;
static LnbLiveHardwareConnections lnbLive;
static LnbRecordHardwareConnections lnbRecord;
static TimeFilterHardwareConnections timeFilter;
static LnbDescramblingHardwareConnections lnbDescrambling;

/*
 * This function takes in a 2d vector of device Id's
 * The n vectors correlate to the ids for n different devices (eg frontends, filters)
 * The resultant 2d vector is every combination of id's with 1 id from each vector
 */
inline vector<vector<string>> generateIdCombinations(vector<vector<string>>& ids) {
    vector<vector<string>> combinations;

    // The index of each vector in ids that will be used in the next combination
    // EG {0, 2} means combo {ids[0][0] ids[1][2]} will be next
    const int size = static_cast<int>(ids.size());
    vector<int> indexes_used_in_combination(size, 0);

    // The vector number from ids whose elements we will cycle through to make combinations.
    // First, start at the right most vector
    int cycled_vector = size - 1;

    while (cycled_vector >= 0) {
        // Make a combination (one at a time)
        vector<string> combo;
        for (size_t i = 0; i < indexes_used_in_combination.size(); ++i) {
            const int combo_index = indexes_used_in_combination[i];
            combo.push_back(ids[i][combo_index]);
        }
        combinations.push_back(combo);

        // Find the right most vector that still has space [elements left] to cycle through and
        // create a combination
        while (cycled_vector >= 0 &&
               indexes_used_in_combination[cycled_vector] == ids[cycled_vector].size() - 1) {
            cycled_vector--;
        }

        // Use this check to avoid segmentation faults
        if (cycled_vector >= 0) {
            // Once found, we have a vector we can cycle through, so increase to its next element
            indexes_used_in_combination[cycled_vector]++;

            // Reset the other vectors to the right to their first element so we can cycle through
            // them again with the new element from cycled vector
            for (size_t i = cycled_vector + 1; i < indexes_used_in_combination.size(); ++i) {
                indexes_used_in_combination[i] = 0;
            }

            // all the vectors to the right were reset, so we can cycle through them again
            // Start at the furthest right vector
            cycled_vector = size - 1;
        }
    }

    return combinations;
}

/*
 * index 0 - playback dvr
 * index 1 - audio filters
 * index 2 - optional section filters
 */
static inline vector<DvrPlaybackHardwareConnections> generatePlaybackCombinations() {
    vector<DvrPlaybackHardwareConnections> combinations;
    vector<string> sectionFilterIds_optional = sectionFilterIds;
    sectionFilterIds_optional.push_back(emptyHardwareId);
    vector<vector<string>> deviceIds{playbackDvrIds, audioFilterIds, sectionFilterIds_optional};

    const int dvrIndex = 0;
    const int audioFilterIndex = 1;
    const int sectionFilterIndex = 2;

    auto idCombinations = generateIdCombinations(deviceIds);
    for (auto& combo : idCombinations) {
        DvrPlaybackHardwareConnections mPlayback;
        mPlayback.dvrId = combo[dvrIndex];
        mPlayback.audioFilterId = combo[audioFilterIndex];
        mPlayback.sectionFilterId = combo[sectionFilterIndex];
        const int videoFilterIndex =
                find(audioFilterIds.begin(), audioFilterIds.end(), mPlayback.audioFilterId) -
                audioFilterIds.begin();
        mPlayback.videoFilterId = videoFilterIds[videoFilterIndex];
        combinations.push_back(mPlayback);
    }

    return combinations;
}

static inline vector<DvrPlaybackHardwareConnections> generatePlaybackConfigs() {
    vector<DvrPlaybackHardwareConnections> playback_configs;
    if (configuredPlayback) {
        ALOGD("Using DVR playback configuration provided.");
        playback_configs = {playback};
    } else {
        ALOGD("Dvr playback not provided. Generating possible combinations. Consider adding it to "
              "the configuration file.");
        playback_configs = generatePlaybackCombinations();
    }

    return playback_configs;
}

/*
 * index 0 - frontends
 * index 1 - audio filters
 * index 2 - lnbs
 */
static inline vector<LnbLiveHardwareConnections> generateLnbLiveCombinations() {
    vector<LnbLiveHardwareConnections> combinations;
    vector<vector<string>> deviceIds{frontendIds, audioFilterIds, lnbIds};

    const int frontendIndex = 0;
    const int audioFilterIndex = 1;
    const int lnbIndex = 2;

    // TODO: Find a better way to vary diseqcMsgs, if at all
    auto idCombinations = generateIdCombinations(deviceIds);
    for (auto& combo : idCombinations) {
        const string feId = combo[frontendIndex];
        auto type = frontendMap[feId].type;
        if (type == FrontendType::DVBS || type == FrontendType::ISDBS ||
            type == FrontendType::ISDBS3) {
            LnbLiveHardwareConnections mLnbLive;
            mLnbLive.frontendId = feId;
            mLnbLive.audioFilterId = combo[audioFilterIndex];
            const int videoFilterIndex =
                    find(audioFilterIds.begin(), audioFilterIds.end(), mLnbLive.audioFilterId) -
                    audioFilterIds.begin();
            mLnbLive.videoFilterId = videoFilterIds[videoFilterIndex];
            mLnbLive.lnbId = combo[lnbIndex];
            mLnbLive.diseqcMsgs = diseqcMsgs;
            combinations.push_back(mLnbLive);
        }
    }

    return combinations;
}

static inline vector<LnbLiveHardwareConnections> generateLnbLiveConfigurations() {
    vector<LnbLiveHardwareConnections> lnbLive_configs;
    if (configuredLnbLive) {
        ALOGD("Using LnbLive configuration provided.");
        lnbLive_configs = {lnbLive};
    } else {
        ALOGD("LnbLive not provided. Generating possible combinations. Consider adding it to the "
              "configuration file.");
        lnbLive_configs = generateLnbLiveCombinations();
    }

    return lnbLive_configs;
}

static inline vector<ScanHardwareConnections> generateScanCombinations() {
    vector<ScanHardwareConnections> combinations;

    for (auto& id : frontendIds) {
        ScanHardwareConnections mScan;
        mScan.frontendId = id;
        combinations.push_back(mScan);
    }

    return combinations;
}

static inline vector<ScanHardwareConnections> generateScanConfigurations() {
    vector<ScanHardwareConnections> scan_configs;
    if (configuredScan) {
        ALOGD("Using scan configuration provided.");
        scan_configs = {scan};
    } else {
        ALOGD("Scan not provided. Generating possible combinations. Consider adding it to "
              "the configuration file.");
        scan_configs = generateScanCombinations();
    }

    return scan_configs;
}

/*
 * index 0 - frontends
 * index 1 - record filter
 * index 2 - Record Dvr
 * index 3 - Lnb
 */
static inline vector<LnbRecordHardwareConnections> generateLnbRecordCombinations() {
    vector<LnbRecordHardwareConnections> combinations;
    vector<vector<string>> deviceIds{frontendIds, recordFilterIds, recordDvrIds, lnbIds};

    const int frontendIndex = 0;
    const int recordFilterIndex = 1;
    const int dvrIndex = 2;
    const int lnbIndex = 3;

    auto idCombinations = generateIdCombinations(deviceIds);
    // TODO : Find a better way to vary diseqcMsgs, if at all
    for (auto& combo : idCombinations) {
        const string feId = combo[frontendIndex];
        auto type = frontendMap[feId].type;
        if (type == FrontendType::DVBS || type == FrontendType::ISDBS ||
            type == FrontendType::ISDBS3) {
            LnbRecordHardwareConnections mLnbRecord;
            mLnbRecord.frontendId = feId;
            mLnbRecord.recordFilterId = combo[recordFilterIndex];
            mLnbRecord.dvrRecordId = combo[dvrIndex];
            mLnbRecord.lnbId = combo[lnbIndex];
            mLnbRecord.diseqcMsgs = diseqcMsgs;
            combinations.push_back(mLnbRecord);
        }
    }

    return combinations;
}

static inline vector<LnbRecordHardwareConnections> generateLnbRecordConfigurations() {
    vector<LnbRecordHardwareConnections> lnbRecord_configs;
    if (configuredLnbRecord) {
        ALOGD("Using LnbRecord configuration provided.");
        lnbRecord_configs = {lnbRecord};
    } else {
        ALOGD("LnbRecord not provided. Generating possible combinations. Consider adding it to "
              "the configuration file.");
        lnbRecord_configs = generateLnbRecordCombinations();
    }

    return lnbRecord_configs;
}

/*
 * index 0 - decramblers
 * index 1 - frontends
 * index 2 - audio filters
 * index 3 - Dvr SW Fe Connections
 * index 4 - DVR Source Connections
 */
static inline vector<DescramblingHardwareConnections> generateDescramblingCombinations() {
    vector<DescramblingHardwareConnections> combinations;
    vector<string> mfrontendIds = frontendIds;
    vector<string> mDvrFeConnectionIds = playbackDvrIds;
    vector<string> mDvrSourceConnectionIds = playbackDvrIds;

    // Add the empty hardware id to each vector to include combinations where these 3 fields might
    // be optional
    mfrontendIds.push_back(emptyHardwareId);
    mDvrFeConnectionIds.push_back(emptyHardwareId);
    mDvrSourceConnectionIds.push_back(emptyHardwareId);

    const int descramblerIndex = 0;
    const int frontendIndex = 1;
    const int audioFilterIndex = 2;
    const int dvrFeIdIndex = 3;
    const int dvrSourceIdIndex = 4;

    vector<vector<string>> deviceIds{descramblerIds, mfrontendIds, audioFilterIds,
                                     mDvrFeConnectionIds, mDvrSourceConnectionIds};
    auto idCombinations = generateIdCombinations(deviceIds);
    for (auto& combo : idCombinations) {
        DescramblingHardwareConnections mDescrambling;
        const string feId = combo[frontendIndex];
        const string dvrSwFeId = combo[dvrFeIdIndex];
        const string dvrSourceId = combo[dvrSourceIdIndex];
        mDescrambling.hasFrontendConnection = feId.compare(emptyHardwareId) == 0 ? false : true;
        if (!mDescrambling.hasFrontendConnection) {
            if (dvrSourceId.compare(emptyHardwareId) == 0) {
                // If combination does not have a frontend or dvr source connection, do not include
                // it
                continue;
            }
        } else {
            if (frontendMap[feId].isSoftwareFe && dvrSwFeId.compare(emptyHardwareId) == 0) {
                // If combination has a software frontend and no dvr->software frontend connection,
                // do not include it
                continue;
            }
        }
        if (dvrSwFeId.compare(dvrSourceId) == 0) {
            // If dvr->software frontend connection is the same as dvr source input to tuner, do not
            // include it.
            continue;
        }
        mDescrambling.frontendId = feId;
        mDescrambling.audioFilterId = combo[audioFilterIndex];
        const int videoFilterIndex =
                find(audioFilterIds.begin(), audioFilterIds.end(), mDescrambling.audioFilterId) -
                audioFilterIds.begin();
        mDescrambling.videoFilterId = videoFilterIds[videoFilterIndex];
        mDescrambling.dvrSoftwareFeId = dvrSwFeId;
        mDescrambling.dvrSourceId = dvrSourceId;
        mDescrambling.descramblerId = combo[descramblerIndex];
        combinations.push_back(mDescrambling);
    }

    return combinations;
}

static inline vector<DescramblingHardwareConnections> generateDescramblingConfigurations() {
    vector<DescramblingHardwareConnections> descrambling_configs;
    if (configuredDescrambling) {
        ALOGD("Using Descrambling configuration provided.");
        descrambling_configs = {descrambling};
    } else {
        ALOGD("Descrambling not provided. Generating possible combinations. Consider adding it to "
              "the "
              "configuration file.");
        descrambling_configs = generateDescramblingCombinations();
    }

    return descrambling_configs;
}

static inline vector<TimeFilterHardwareConnections> generateTimeFilterCombinations() {
    vector<TimeFilterHardwareConnections> combinations;

    for (auto& id : timeFilterIds) {
        TimeFilterHardwareConnections mTimeFilter;
        mTimeFilter.timeFilterId = id;
        combinations.push_back(mTimeFilter);
    }

    return combinations;
}

static inline vector<TimeFilterHardwareConnections> generateTimeFilterConfigurations() {
    vector<TimeFilterHardwareConnections> timeFilter_configs;
    if (configuredTimeFilter) {
        ALOGD("Using TimeFilter configuration provided.");
        timeFilter_configs = {timeFilter};
    } else {
        ALOGD("TimeFilter not provided. Generating possible combinations. Consider adding it to "
              "the "
              "configuration file.");
        timeFilter_configs = generateTimeFilterCombinations();
    }

    return timeFilter_configs;
}

/*
 * index 0 - frontends
 * index 1 - record dvrs
 * index 2 - record filters
 */
static inline vector<DvrRecordHardwareConnections> generateRecordCombinations() {
    vector<DvrRecordHardwareConnections> combinations;

    const int frontendIdIndex = 0;
    const int recordDvrIndex = 1;
    const int recordFilterIndex = 2;

    vector<vector<string>> deviceIds{frontendIds, recordDvrIds, recordFilterIds};

    auto idCombinations = generateIdCombinations(deviceIds);
    for (auto& combo : idCombinations) {
        DvrRecordHardwareConnections mRecord;
        const string feId = combo[frontendIdIndex];
        mRecord.hasFrontendConnection = true;
        if (frontendMap[feId].isSoftwareFe) {
            // If we have a software frontend, do not include configuration for testing.
            continue;
        }
        mRecord.frontendId = feId;
        mRecord.support = true;
        mRecord.dvrSourceId = emptyHardwareId;
        mRecord.dvrSoftwareFeId = emptyHardwareId;
        mRecord.recordFilterId = combo[recordFilterIndex];
        mRecord.dvrRecordId = combo[recordDvrIndex];
        combinations.push_back(mRecord);
    }

    return combinations;
}

static inline vector<DvrRecordHardwareConnections> generateRecordConfigurations() {
    vector<DvrRecordHardwareConnections> record_configs;
    if (configuredRecord) {
        ALOGD("Using Record configuration provided.");
        record_configs = {record};
    } else {
        ALOGD("Record not provided. Generating possible combinations. Consider adding it to "
              "the "
              "configuration file.");
        record_configs = generateRecordCombinations();
    }

    return record_configs;
}

/*
 * index 0 - frontends
 * index 1 - audio filters
 * index 2 - playback dvrs
 * index 3 - section Filters
 */
static inline vector<LiveBroadcastHardwareConnections> generateLiveCombinations() {
    vector<LiveBroadcastHardwareConnections> combinations;
    vector<string> mSectionFilterIds = sectionFilterIds;
    vector<string> mDvrSwConnectionIds = playbackDvrIds;

    // Adding the empty hardware id to cover cases where fields are optional
    mSectionFilterIds.push_back(emptyHardwareId);
    mDvrSwConnectionIds.push_back(emptyHardwareId);

    const int frontendIdIndex = 0;
    const int audioFilterIdIndex = 1;
    const int dvrSwConnectionIdIndex = 2;
    const int sectionFilterIdIndex = 3;

    vector<vector<string>> deviceIds{frontendIds, audioFilterIds, mDvrSwConnectionIds,
                                     mSectionFilterIds};

    auto idCombinations = generateIdCombinations(deviceIds);
    for (auto& combo : idCombinations) {
        LiveBroadcastHardwareConnections mLive;
        const string feId = combo[frontendIdIndex];
        const string dvrSwConnectionId = combo[dvrSwConnectionIdIndex];
        mLive.hasFrontendConnection = true;

        if (frontendMap[feId].isSoftwareFe && dvrSwConnectionId.compare(emptyHardwareId) == 0) {
            // If the frontend is a software frontend and there is no dvr playback connected, do not
            // include configuration
            continue;
        }
        mLive.frontendId = feId;
        mLive.dvrSoftwareFeId = dvrSwConnectionId;
        mLive.audioFilterId = combo[audioFilterIdIndex];
        const int videoFilterIdIndex =
                find(audioFilterIds.begin(), audioFilterIds.end(), mLive.audioFilterId) -
                audioFilterIds.begin();
        mLive.videoFilterId = videoFilterIds[videoFilterIdIndex];
        mLive.sectionFilterId = combo[sectionFilterIdIndex];

        if (pcrFilterIds.empty()) {
            // If pcr Filters have not been provided, set it to empty hardware id
            mLive.pcrFilterId = emptyHardwareId;
        } else {
            // If pcr Filters have been provided, use the first index if there is only 1, or choose
            // the filter that corresponds to the correct audio and video filter pair
            const int pcrFilterIdIndex = pcrFilterIds.size() == 1 ? 0 : videoFilterIdIndex;
            mLive.pcrFilterId = pcrFilterIds[pcrFilterIdIndex];
        }

        combinations.push_back(mLive);
    }

    return combinations;
}

static inline vector<LiveBroadcastHardwareConnections> generateLiveConfigurations() {
    vector<LiveBroadcastHardwareConnections> live_configs;
    if (configuredLive) {
        ALOGD("Using Live configuration provided.");
        live_configs = {live};
    } else {
        ALOGD("Live not provided. Generating possible combinations. Consider adding it to "
              "the "
              "configuration file.");
        live_configs = generateLiveCombinations();
    }

    return live_configs;
}

static inline vector<LnbDescramblingHardwareConnections> generateLnbDescramblingCombinations() {
    vector<LnbDescramblingHardwareConnections> combinations;
    vector<vector<string>> deviceIds{frontendIds, audioFilterIds, lnbIds, descramblerIds};

    const int frontendIdIndex = 0;
    const int audioFilterIdIndex = 1;
    const int lnbIdIndex = 2;
    const int descramblerIdIndex = 3;

    auto idCombinations = generateIdCombinations(deviceIds);
    // TODO : Find a better way to vary diseqcMsgs, if at all
    for (auto& combo : idCombinations) {
        const string feId = combo[frontendIdIndex];
        auto type = frontendMap[feId].type;
        if (type == FrontendType::DVBS || type == FrontendType::ISDBS ||
            type == FrontendType::ISDBS3) {
            LnbDescramblingHardwareConnections mLnbDescrambling;
            mLnbDescrambling.support = true;
            mLnbDescrambling.frontendId = feId;
            mLnbDescrambling.audioFilterId = combo[audioFilterIdIndex];
            const int videoFilterIdIndex = find(audioFilterIds.begin(), audioFilterIds.end(),
                                                mLnbDescrambling.audioFilterId) -
                                           audioFilterIds.begin();
            mLnbDescrambling.videoFilterId = videoFilterIds[videoFilterIdIndex];
            mLnbDescrambling.lnbId = combo[lnbIdIndex];
            mLnbDescrambling.descramblerId = combo[descramblerIdIndex];
            mLnbDescrambling.diseqcMsgs = diseqcMsgs;
            combinations.push_back(mLnbDescrambling);
        }
    }

    return combinations;
}

static inline vector<LnbDescramblingHardwareConnections> generateLnbDescramblingConfigurations() {
    vector<LnbDescramblingHardwareConnections> lnbDescrambling_configs;
    if (configuredLnbDescrambling) {
        ALOGD("Using LnbDescrambling configuration provided");
        lnbDescrambling_configs = {lnbDescrambling};
    } else {
        ALOGD("LnbDescrambling not provided. Generating possible combinations. Consider adding it "
              "to the configuration file.");
        lnbDescrambling_configs = generateLnbDescramblingCombinations();
    }

    return lnbDescrambling_configs;
}

/** Config all the frontends that would be used in the tests */
inline void initFrontendConfig() {
    // The test will use the internal default fe when default fe is connected to any data flow
    // without overriding in the xml config.
    string defaultFeId = "FE_DEFAULT";
    FrontendDvbtSettings dvbtSettings{
            .frequency = 578000000,
            .transmissionMode = FrontendDvbtTransmissionMode::AUTO,
            .bandwidth = FrontendDvbtBandwidth::BANDWIDTH_8MHZ,
            .isHighPriority = true,
    };
    frontendMap[defaultFeId].type = FrontendType::DVBT;
    frontendMap[defaultFeId].settings.set<FrontendSettings::Tag::dvbt>(dvbtSettings);

    vector<FrontendStatusType> types;
    types.push_back(FrontendStatusType::UEC);
    types.push_back(FrontendStatusType::IS_MISO);

    vector<FrontendStatus> statuses;
    FrontendStatus status;
    status.set<FrontendStatus::Tag::uec>(4);
    statuses.push_back(status);
    status.set<FrontendStatus::Tag::isMiso>(true);
    statuses.push_back(status);

    frontendMap[defaultFeId].tuneStatusTypes = types;
    frontendMap[defaultFeId].expectTuneStatuses = statuses;
    frontendMap[defaultFeId].isSoftwareFe = true;
    frontendMap[defaultFeId].canConnectToCiCam = true;
    frontendMap[defaultFeId].ciCamId = 0;
    frontendMap[defaultFeId].supportBlindScan = true;
    FrontendDvbtSettings dvbt;
    dvbt.transmissionMode = FrontendDvbtTransmissionMode::MODE_8K_E;
    frontendMap[defaultFeId].settings.set<FrontendSettings::Tag::dvbt>(dvbt);
    // Read customized config
    TunerTestingConfigAidlReader1_0::readFrontendConfig1_0(frontendMap);
};

inline void initFilterConfig() {
    // The test will use the internal default filter when default filter is connected to any
    // data flow without overriding in the xml config.
    string defaultAudioFilterId = "FILTER_AUDIO_DEFAULT";
    string defaultVideoFilterId = "FILTER_VIDEO_DEFAULT";

    filterMap[defaultVideoFilterId].type.mainType = DemuxFilterMainType::TS;
    filterMap[defaultVideoFilterId].type.subType.set<DemuxFilterSubType::Tag::tsFilterType>(
            DemuxTsFilterType::VIDEO);
    filterMap[defaultVideoFilterId].bufferSize = FMQ_SIZE_16M;
    filterMap[defaultVideoFilterId].settings =
            DemuxFilterSettings::make<DemuxFilterSettings::Tag::ts>();
    filterMap[defaultVideoFilterId].settings.get<DemuxFilterSettings::Tag::ts>().tpid = 256;
    DemuxFilterAvSettings video;
    video.isPassthrough = false;
    filterMap[defaultVideoFilterId]
            .settings.get<DemuxFilterSettings::Tag::ts>()
            .filterSettings.set<DemuxTsFilterSettingsFilterSettings::Tag::av>(video);
    filterMap[defaultVideoFilterId].monitorEventTypes =
            static_cast<int32_t>(DemuxFilterMonitorEventType::SCRAMBLING_STATUS) |
            static_cast<int32_t>(DemuxFilterMonitorEventType::IP_CID_CHANGE);
    filterMap[defaultVideoFilterId].streamType.set<AvStreamType::Tag::video>(
            VideoStreamType::MPEG1);

    filterMap[defaultAudioFilterId].type.mainType = DemuxFilterMainType::TS;
    filterMap[defaultAudioFilterId].type.subType.set<DemuxFilterSubType::Tag::tsFilterType>(
            DemuxTsFilterType::AUDIO);
    filterMap[defaultAudioFilterId].bufferSize = FMQ_SIZE_16M;
    filterMap[defaultAudioFilterId].settings =
            DemuxFilterSettings::make<DemuxFilterSettings::Tag::ts>();
    filterMap[defaultAudioFilterId].settings.get<DemuxFilterSettings::Tag::ts>().tpid = 256;
    DemuxFilterAvSettings audio;
    audio.isPassthrough = false;
    filterMap[defaultAudioFilterId]
            .settings.get<DemuxFilterSettings::Tag::ts>()
            .filterSettings.set<DemuxTsFilterSettingsFilterSettings::Tag::av>(audio);
    filterMap[defaultAudioFilterId].monitorEventTypes =
            static_cast<int32_t>(DemuxFilterMonitorEventType::SCRAMBLING_STATUS) |
            static_cast<int32_t>(DemuxFilterMonitorEventType::IP_CID_CHANGE);
    filterMap[defaultAudioFilterId].streamType.set<AvStreamType::Tag::audio>(AudioStreamType::MP3);
    // Read customized config
    TunerTestingConfigAidlReader1_0::readFilterConfig1_0(filterMap);
};

/** Config all the dvrs that would be used in the tests */
inline void initDvrConfig() {
    // Read customized config
    TunerTestingConfigAidlReader1_0::readDvrConfig1_0(dvrMap);
};

inline void initTimeFilterConfig() {
    // Read customized config
    TunerTestingConfigAidlReader1_0::readTimeFilterConfig1_0(timeFilterMap);
};

inline void initDescramblerConfig() {
    // Read customized config
    TunerTestingConfigAidlReader1_0::readDescramblerConfig1_0(descramblerMap);
}

inline void initLnbConfig() {
    // Read customized config
    TunerTestingConfigAidlReader1_0::readLnbConfig1_0(lnbMap);
};

inline void initDiseqcMsgsConfig() {
    // Read customized config
    TunerTestingConfigAidlReader1_0::readDiseqcMessages(diseqcMsgMap);
};

inline void determineScan() {
    if (!frontendMap.empty()) {
        scan.hasFrontendConnection = true;
        ALOGD("Can support scan");
    }
}

inline void determineTimeFilter() {
    if (!timeFilterMap.empty()) {
        timeFilter.support = true;
        ALOGD("Can support time filter");
    }
}

inline void determineDvrPlayback() {
    if (!playbackDvrIds.empty() && !audioFilterIds.empty() && !videoFilterIds.empty()) {
        playback.support = true;
        ALOGD("Can support dvr playback");
    }
}

inline void determineLnbLive() {
    if (!audioFilterIds.empty() && !videoFilterIds.empty() && !frontendMap.empty() &&
        !lnbMap.empty()) {
        lnbLive.support = true;
        ALOGD("Can support lnb live");
    }
}

inline void determineLnbRecord() {
    if (!frontendMap.empty() && !recordFilterIds.empty() && !recordDvrIds.empty() &&
        !lnbMap.empty()) {
        lnbRecord.support = true;
        ALOGD("Can support lnb record");
    }
}

inline void determineLive() {
    if (videoFilterIds.empty() || audioFilterIds.empty() || frontendMap.empty()) {
        return;
    }
    if (!hasHwFe) {
        if (hasSwFe) {
            if (dvrMap.empty()) {
                ALOGD("Cannot configure Live. Only software frontends and no dvr connections.");
                return;
            }
            // Live is available if there is SW FE and some DVR is attached.
        } else {
            // We will arrive here because frontendMap won't be empty since
            // there will be at least a default frontend declared. But the
            // default frontend doesn't count as "hasSwFe".
            ALOGD("Cannot configure Live. No frontend declared at all.");
            return;
        }
    }
    ALOGD("Can support live");
    live.hasFrontendConnection = true;
}

inline void determineDescrambling() {
    if (descramblerMap.empty() || audioFilterIds.empty() || videoFilterIds.empty()) {
        return;
    }
    if (frontendMap.empty() && playbackDvrIds.empty()) {
        ALOGD("Cannot configure descrambling. No frontends or playback dvr's");
        return;
    }
    if (hasSwFe && !hasHwFe && playbackDvrIds.empty()) {
        ALOGD("cannot configure descrambling. Only SW frontends and no playback dvr's");
        return;
    }
    ALOGD("Can support descrambling");
    descrambling.support = true;
}

inline void determineDvrRecord() {
    if (recordDvrIds.empty() || recordFilterIds.empty()) {
        return;
    }
    if (frontendMap.empty() && playbackDvrIds.empty()) {
        ALOGD("Cannot support dvr record. No frontends and no playback dvr's");
        return;
    }
    if (hasSwFe && !hasHwFe && playbackDvrIds.empty()) {
        ALOGD("Cannot support dvr record. Only SW frontends and no playback dvr's");
        return;
    }
    ALOGD("Can support dvr record.");
    record.support = true;
}

inline void determineLnbDescrambling() {
    if (frontendIds.empty() || audioFilterIds.empty() || videoFilterIds.empty() || lnbIds.empty() ||
        descramblerIds.empty()) {
        return;
    }
    ALOGD("Can support LnbDescrambling.");
    lnbDescrambling.support = true;
}

/** Read the vendor configurations of which hardware to use for each test cases/data flows */
inline void connectHardwaresToTestCases() {
    TunerTestingConfigAidlReader1_0::connectLiveBroadcast(live);
    TunerTestingConfigAidlReader1_0::connectScan(scan);
    TunerTestingConfigAidlReader1_0::connectDvrRecord(record);
    TunerTestingConfigAidlReader1_0::connectTimeFilter(timeFilter);
    TunerTestingConfigAidlReader1_0::connectDescrambling(descrambling);
    TunerTestingConfigAidlReader1_0::connectLnbLive(lnbLive);
    TunerTestingConfigAidlReader1_0::connectLnbRecord(lnbRecord);
    TunerTestingConfigAidlReader1_0::connectDvrPlayback(playback);
    TunerTestingConfigAidlReader1_0::connectLnbDescrambling(lnbDescrambling);
};

inline void determineDataFlows() {
    determineScan();
    determineTimeFilter();
    determineDvrPlayback();
    determineLnbLive();
    determineLnbRecord();
    determineLive();
    determineDescrambling();
    determineDvrRecord();
    determineLnbDescrambling();
}

inline bool validateConnections() {
    if (record.support && !record.hasFrontendConnection &&
        record.dvrSourceId.compare(emptyHardwareId) == 0) {
        ALOGW("[vts config] Record must support either a DVR source or a Frontend source.");
        return false;
    }
    bool feIsValid = live.hasFrontendConnection
                             ? frontendMap.find(live.frontendId) != frontendMap.end()
                             : true;
    feIsValid &= scan.hasFrontendConnection ? frontendMap.find(scan.frontendId) != frontendMap.end()
                                            : true;
    feIsValid &= record.support && record.hasFrontendConnection
                         ? frontendMap.find(record.frontendId) != frontendMap.end()
                         : true;
    feIsValid &= descrambling.support && descrambling.hasFrontendConnection
                         ? frontendMap.find(descrambling.frontendId) != frontendMap.end()
                         : true;

    feIsValid &= lnbLive.support ? frontendMap.find(lnbLive.frontendId) != frontendMap.end() : true;

    feIsValid &=
            lnbRecord.support ? frontendMap.find(lnbRecord.frontendId) != frontendMap.end() : true;

    feIsValid &= lnbDescrambling.support
                         ? frontendMap.find(lnbDescrambling.frontendId) != frontendMap.end()
                         : true;

    if (!feIsValid) {
        ALOGW("[vts config] dynamic config fe connection is invalid.");
        return false;
    }

    bool dvrIsValid = frontendMap[live.frontendId].isSoftwareFe
                              ? dvrMap.find(live.dvrSoftwareFeId) != dvrMap.end()
                              : true;

    if (record.support) {
        if (record.hasFrontendConnection) {
            if (frontendMap[record.frontendId].isSoftwareFe) {
                dvrIsValid &= dvrMap.find(record.dvrSoftwareFeId) != dvrMap.end();
            }
        } else {
            dvrIsValid &= dvrMap.find(record.dvrSourceId) != dvrMap.end();
        }
        dvrIsValid &= dvrMap.find(record.dvrRecordId) != dvrMap.end();
    }

    if (descrambling.support) {
        if (descrambling.hasFrontendConnection) {
            if (frontendMap[descrambling.frontendId].isSoftwareFe) {
                dvrIsValid &= dvrMap.find(descrambling.dvrSoftwareFeId) != dvrMap.end();
            }
        } else {
            dvrIsValid &= dvrMap.find(descrambling.dvrSourceId) != dvrMap.end();
        }
    }

    dvrIsValid &= lnbRecord.support ? dvrMap.find(lnbRecord.dvrRecordId) != dvrMap.end() : true;

    dvrIsValid &= playback.support ? dvrMap.find(playback.dvrId) != dvrMap.end() : true;

    if (!dvrIsValid) {
        ALOGW("[vts config] dynamic config dvr connection is invalid.");
        return false;
    }

    bool filterIsValid = (live.hasFrontendConnection)
                             ? filterMap.find(live.audioFilterId) != filterMap.end() &&
                               filterMap.find(live.videoFilterId) != filterMap.end()
                             : true;
    filterIsValid &=
            record.support ? filterMap.find(record.recordFilterId) != filterMap.end() : true;

    filterIsValid &= descrambling.support
                             ? filterMap.find(descrambling.videoFilterId) != filterMap.end() &&
                                       filterMap.find(descrambling.audioFilterId) != filterMap.end()
                             : true;

    for (auto& filterId : descrambling.extraFilters) {
        filterIsValid &= filterMap.find(filterId) != filterMap.end();
    }

    filterIsValid &= lnbLive.support
                             ? filterMap.find(lnbLive.audioFilterId) != filterMap.end() &&
                                       filterMap.find(lnbLive.videoFilterId) != filterMap.end()
                             : true;

    filterIsValid &=
            lnbRecord.support ? filterMap.find(lnbRecord.recordFilterId) != filterMap.end() : true;

    for (auto& filterId : lnbRecord.extraFilters) {
        filterIsValid &= filterMap.find(filterId) != filterMap.end();
    }

    for (auto& filterId : lnbLive.extraFilters) {
        filterIsValid &= filterMap.find(filterId) != filterMap.end();
    }

    filterIsValid &= playback.support
                             ? filterMap.find(playback.audioFilterId) != filterMap.end() &&
                                       filterMap.find(playback.videoFilterId) != filterMap.end()
                             : true;
    filterIsValid &= playback.sectionFilterId.compare(emptyHardwareId) == 0
                             ? true
                             : filterMap.find(playback.sectionFilterId) != filterMap.end();

    for (auto& filterId : playback.extraFilters) {
        filterIsValid &=
                playback.hasExtraFilters ? filterMap.find(filterId) != filterMap.end() : true;
    }

    filterIsValid &=
            lnbDescrambling.support
                    ? filterMap.find(lnbDescrambling.audioFilterId) != filterMap.end() &&
                              filterMap.find(lnbDescrambling.videoFilterId) != filterMap.end()
                    : true;

    if (!filterIsValid) {
        ALOGW("[vts config] dynamic config filter connection is invalid.");
        return false;
    }

    if (audioFilterIds.size() != videoFilterIds.size()) {
        ALOGW("[vts config] the number of audio and video filters should be equal");
        return false;
    }

    if (!pcrFilterIds.empty() && pcrFilterIds.size() != 1 &&
        pcrFilterIds.size() != audioFilterIds.size()) {
        ALOGW("[vts config] When more than 1 pcr filter is configured, the number of pcr filters "
              "must equal the number of audio and video filters.");
        return false;
    }

    bool timeFilterIsValid =
            timeFilter.support ? timeFilterMap.find(timeFilter.timeFilterId) != timeFilterMap.end()
                               : true;

    if (!timeFilterIsValid) {
        ALOGW("[vts config] dynamic config time filter connection is invalid.");
    }

    bool descramblerIsValid =
            descrambling.support
                    ? descramblerMap.find(descrambling.descramblerId) != descramblerMap.end()
                    : true;

    descramblerIsValid &=
            lnbDescrambling.support
                    ? descramblerMap.find(lnbDescrambling.descramblerId) != descramblerMap.end()
                    : true;

    if (!descramblerIsValid) {
        ALOGW("[vts config] dynamic config descrambler connection is invalid.");
        return false;
    }

    bool lnbIsValid = lnbLive.support ? lnbMap.find(lnbLive.lnbId) != lnbMap.end() : true;

    lnbIsValid &= lnbRecord.support ? lnbMap.find(lnbRecord.lnbId) != lnbMap.end() : true;

    lnbIsValid &=
            lnbDescrambling.support ? lnbMap.find(lnbDescrambling.lnbId) != lnbMap.end() : true;

    if (!lnbIsValid) {
        ALOGW("[vts config] dynamic config lnb connection is invalid.");
        return false;
    }

    bool diseqcMsgsIsValid = true;

    for (auto& msg : lnbRecord.diseqcMsgs) {
        diseqcMsgsIsValid &= diseqcMsgMap.find(msg) != diseqcMsgMap.end();
    }

    for (auto& msg : lnbLive.diseqcMsgs) {
        diseqcMsgsIsValid &= diseqcMsgMap.find(msg) != diseqcMsgMap.end();
    }

    for (auto& msg : lnbDescrambling.diseqcMsgs) {
        diseqcMsgsIsValid &= diseqcMsgMap.find(msg) != diseqcMsgMap.end();
    }

    if (!diseqcMsgsIsValid) {
        ALOGW("[vts config] dynamic config diseqcMsg is invalid.");
        return false;
    }

    return true;
}
