/*
 * Copyright 2013, 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 "VideoFormats"
#include <utils/Log.h>

#include "VideoFormats.h"

#include <media/stagefright/foundation/ADebug.h>

namespace android {

// static
const VideoFormats::config_t VideoFormats::mResolutionTable[][32] = {
    {
        // CEA Resolutions
        { 640, 480, 60, false, 0, 0},
        { 720, 480, 60, false, 0, 0},
        { 720, 480, 60, true, 0, 0},
        { 720, 576, 50, false, 0, 0},
        { 720, 576, 50, true, 0, 0},
        { 1280, 720, 30, false, 0, 0},
        { 1280, 720, 60, false, 0, 0},
        { 1920, 1080, 30, false, 0, 0},
        { 1920, 1080, 60, false, 0, 0},
        { 1920, 1080, 60, true, 0, 0},
        { 1280, 720, 25, false, 0, 0},
        { 1280, 720, 50, false, 0, 0},
        { 1920, 1080, 25, false, 0, 0},
        { 1920, 1080, 50, false, 0, 0},
        { 1920, 1080, 50, true, 0, 0},
        { 1280, 720, 24, false, 0, 0},
        { 1920, 1080, 24, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
    },
    {
        // VESA Resolutions
        { 800, 600, 30, false, 0, 0},
        { 800, 600, 60, false, 0, 0},
        { 1024, 768, 30, false, 0, 0},
        { 1024, 768, 60, false, 0, 0},
        { 1152, 864, 30, false, 0, 0},
        { 1152, 864, 60, false, 0, 0},
        { 1280, 768, 30, false, 0, 0},
        { 1280, 768, 60, false, 0, 0},
        { 1280, 800, 30, false, 0, 0},
        { 1280, 800, 60, false, 0, 0},
        { 1360, 768, 30, false, 0, 0},
        { 1360, 768, 60, false, 0, 0},
        { 1366, 768, 30, false, 0, 0},
        { 1366, 768, 60, false, 0, 0},
        { 1280, 1024, 30, false, 0, 0},
        { 1280, 1024, 60, false, 0, 0},
        { 1400, 1050, 30, false, 0, 0},
        { 1400, 1050, 60, false, 0, 0},
        { 1440, 900, 30, false, 0, 0},
        { 1440, 900, 60, false, 0, 0},
        { 1600, 900, 30, false, 0, 0},
        { 1600, 900, 60, false, 0, 0},
        { 1600, 1200, 30, false, 0, 0},
        { 1600, 1200, 60, false, 0, 0},
        { 1680, 1024, 30, false, 0, 0},
        { 1680, 1024, 60, false, 0, 0},
        { 1680, 1050, 30, false, 0, 0},
        { 1680, 1050, 60, false, 0, 0},
        { 1920, 1200, 30, false, 0, 0},
        { 1920, 1200, 60, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
    },
    {
        // HH Resolutions
        { 800, 480, 30, false, 0, 0},
        { 800, 480, 60, false, 0, 0},
        { 854, 480, 30, false, 0, 0},
        { 854, 480, 60, false, 0, 0},
        { 864, 480, 30, false, 0, 0},
        { 864, 480, 60, false, 0, 0},
        { 640, 360, 30, false, 0, 0},
        { 640, 360, 60, false, 0, 0},
        { 960, 540, 30, false, 0, 0},
        { 960, 540, 60, false, 0, 0},
        { 848, 480, 30, false, 0, 0},
        { 848, 480, 60, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
        { 0, 0, 0, false, 0, 0},
    }
};

VideoFormats::VideoFormats() {
    memcpy(mConfigs, mResolutionTable, sizeof(mConfigs));

    for (size_t i = 0; i < kNumResolutionTypes; ++i) {
        mResolutionEnabled[i] = 0;
    }

    setNativeResolution(RESOLUTION_CEA, 0);  // default to 640x480 p60
}

void VideoFormats::setNativeResolution(ResolutionType type, size_t index) {
    CHECK_LT(type, kNumResolutionTypes);
    CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));

    mNativeType = type;
    mNativeIndex = index;

    setResolutionEnabled(type, index);
}

void VideoFormats::getNativeResolution(
        ResolutionType *type, size_t *index) const {
    *type = mNativeType;
    *index = mNativeIndex;
}

void VideoFormats::disableAll() {
    for (size_t i = 0; i < kNumResolutionTypes; ++i) {
        mResolutionEnabled[i] = 0;
        for (size_t j = 0; j < 32; j++) {
            mConfigs[i][j].profile = mConfigs[i][j].level = 0;
        }
    }
}

void VideoFormats::enableAll() {
    for (size_t i = 0; i < kNumResolutionTypes; ++i) {
        mResolutionEnabled[i] = 0xffffffff;
        for (size_t j = 0; j < 32; j++) {
            mConfigs[i][j].profile = (1ul << PROFILE_CBP);
            mConfigs[i][j].level = (1ul << LEVEL_31);
        }
    }
}

void VideoFormats::enableResolutionUpto(
        ResolutionType type, size_t index,
        ProfileType profile, LevelType level) {
    size_t width, height, fps, score;
    bool interlaced;
    if (!GetConfiguration(type, index, &width, &height,
            &fps, &interlaced)) {
        ALOGE("Maximum resolution not found!");
        return;
    }
    score = width * height * fps * (!interlaced + 1);
    for (size_t i = 0; i < kNumResolutionTypes; ++i) {
        for (size_t j = 0; j < 32; j++) {
            if (GetConfiguration((ResolutionType)i, j,
                    &width, &height, &fps, &interlaced)
                    && score >= width * height * fps * (!interlaced + 1)) {
                setResolutionEnabled((ResolutionType)i, j);
                setProfileLevel((ResolutionType)i, j, profile, level);
            }
        }
    }
}

void VideoFormats::setResolutionEnabled(
        ResolutionType type, size_t index, bool enabled) {
    CHECK_LT(type, kNumResolutionTypes);
    CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));

    if (enabled) {
        mResolutionEnabled[type] |= (1ul << index);
        mConfigs[type][index].profile = (1ul << PROFILE_CBP);
        mConfigs[type][index].level = (1ul << LEVEL_31);
    } else {
        mResolutionEnabled[type] &= ~(1ul << index);
        mConfigs[type][index].profile = 0;
        mConfigs[type][index].level = 0;
    }
}

void VideoFormats::setProfileLevel(
        ResolutionType type, size_t index,
        ProfileType profile, LevelType level) {
    CHECK_LT(type, kNumResolutionTypes);
    CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));

    mConfigs[type][index].profile = (1ul << profile);
    mConfigs[type][index].level = (1ul << level);
}

void VideoFormats::getProfileLevel(
        ResolutionType type, size_t index,
        ProfileType *profile, LevelType *level) const{
    CHECK_LT(type, kNumResolutionTypes);
    CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));

    int i, bestProfile = -1, bestLevel = -1;

    for (i = 0; i < kNumProfileTypes; ++i) {
        if (mConfigs[type][index].profile & (1ul << i)) {
            bestProfile = i;
        }
    }

    for (i = 0; i < kNumLevelTypes; ++i) {
        if (mConfigs[type][index].level & (1ul << i)) {
            bestLevel = i;
        }
    }

    if (bestProfile == -1 || bestLevel == -1) {
        ALOGE("Profile or level not set for resolution type %d, index %zu",
                type, index);
        bestProfile = PROFILE_CBP;
        bestLevel = LEVEL_31;
    }

    *profile = (ProfileType) bestProfile;
    *level = (LevelType) bestLevel;
}

bool VideoFormats::isResolutionEnabled(
        ResolutionType type, size_t index) const {
    CHECK_LT(type, kNumResolutionTypes);
    CHECK(GetConfiguration(type, index, NULL, NULL, NULL, NULL));

    return mResolutionEnabled[type] & (1ul << index);
}

// static
bool VideoFormats::GetConfiguration(
        ResolutionType type,
        size_t index,
        size_t *width, size_t *height, size_t *framesPerSecond,
        bool *interlaced) {
    CHECK_LT(type, kNumResolutionTypes);

    if (index >= 32) {
        return false;
    }

    const config_t *config = &mResolutionTable[type][index];

    if (config->width == 0) {
        return false;
    }

    if (width) {
        *width = config->width;
    }

    if (height) {
        *height = config->height;
    }

    if (framesPerSecond) {
        *framesPerSecond = config->framesPerSecond;
    }

    if (interlaced) {
        *interlaced = config->interlaced;
    }

    return true;
}

bool VideoFormats::parseH264Codec(const char *spec) {
    unsigned profile, level, res[3];

    if (sscanf(
            spec,
            "%02x %02x %08X %08X %08X",
            &profile,
            &level,
            &res[0],
            &res[1],
            &res[2]) != 5) {
        return false;
    }

    for (size_t i = 0; i < kNumResolutionTypes; ++i) {
        for (size_t j = 0; j < 32; ++j) {
            if (res[i] & (1ul << j)){
                mResolutionEnabled[i] |= (1ul << j);
                if (profile > mConfigs[i][j].profile) {
                    // prefer higher profile (even if level is lower)
                    mConfigs[i][j].profile = profile;
                    mConfigs[i][j].level = level;
                } else if (profile == mConfigs[i][j].profile &&
                           level > mConfigs[i][j].level) {
                    mConfigs[i][j].level = level;
                }
            }
        }
    }

    return true;
}

// static
bool VideoFormats::GetProfileLevel(
        ProfileType profile, LevelType level, unsigned *profileIdc,
        unsigned *levelIdc, unsigned *constraintSet) {
    CHECK_LT(profile, kNumProfileTypes);
    CHECK_LT(level, kNumLevelTypes);

    static const unsigned kProfileIDC[kNumProfileTypes] = {
        66,     // PROFILE_CBP
        100,    // PROFILE_CHP
    };

    static const unsigned kLevelIDC[kNumLevelTypes] = {
        31,     // LEVEL_31
        32,     // LEVEL_32
        40,     // LEVEL_40
        41,     // LEVEL_41
        42,     // LEVEL_42
    };

    static const unsigned kConstraintSet[kNumProfileTypes] = {
        0xc0,   // PROFILE_CBP
        0x0c,   // PROFILE_CHP
    };

    if (profileIdc) {
        *profileIdc = kProfileIDC[profile];
    }

    if (levelIdc) {
        *levelIdc = kLevelIDC[level];
    }

    if (constraintSet) {
        *constraintSet = kConstraintSet[profile];
    }

    return true;
}

bool VideoFormats::parseFormatSpec(const char *spec) {
    CHECK_EQ(kNumResolutionTypes, 3);

    disableAll();

    unsigned native, dummy;
    size_t size = strlen(spec);
    size_t offset = 0;

    if (sscanf(spec, "%02x %02x ", &native, &dummy) != 2) {
        return false;
    }

    offset += 6; // skip native and preferred-display-mode-supported
    CHECK_LE(offset + 58, size);
    while (offset < size) {
        parseH264Codec(spec + offset);
        offset += 60; // skip H.264-codec + ", "
    }

    mNativeIndex = native >> 3;
    mNativeType = (ResolutionType)(native & 7);

    bool success;
    if (mNativeType >= kNumResolutionTypes) {
        success = false;
    } else {
        success = GetConfiguration(
                mNativeType, mNativeIndex, NULL, NULL, NULL, NULL);
    }

    if (!success) {
        ALOGW("sink advertised an illegal native resolution, fortunately "
              "this value is ignored for the time being...");
    }

    return true;
}

AString VideoFormats::getFormatSpec(bool forM4Message) const {
    CHECK_EQ(kNumResolutionTypes, 3);

    // wfd_video_formats:
    // 1 byte "native"
    // 1 byte "preferred-display-mode-supported" 0 or 1
    // one or more avc codec structures
    //   1 byte profile
    //   1 byte level
    //   4 byte CEA mask
    //   4 byte VESA mask
    //   4 byte HH mask
    //   1 byte latency
    //   2 byte min-slice-slice
    //   2 byte slice-enc-params
    //   1 byte framerate-control-support
    //   max-hres (none or 2 byte)
    //   max-vres (none or 2 byte)

    return AStringPrintf(
            "%02x 00 %02x %02x %08x %08x %08x 00 0000 0000 00 none none",
            forM4Message ? 0x00 : ((mNativeIndex << 3) | mNativeType),
            mConfigs[mNativeType][mNativeIndex].profile,
            mConfigs[mNativeType][mNativeIndex].level,
            mResolutionEnabled[0],
            mResolutionEnabled[1],
            mResolutionEnabled[2]);
}

// static
bool VideoFormats::PickBestFormat(
        const VideoFormats &sinkSupported,
        const VideoFormats &sourceSupported,
        ResolutionType *chosenType,
        size_t *chosenIndex,
        ProfileType *chosenProfile,
        LevelType *chosenLevel) {
#if 0
    // Support for the native format is a great idea, the spec includes
    // these features, but nobody supports it and the tests don't validate it.

    ResolutionType nativeType;
    size_t nativeIndex;
    sinkSupported.getNativeResolution(&nativeType, &nativeIndex);
    if (sinkSupported.isResolutionEnabled(nativeType, nativeIndex)) {
        if (sourceSupported.isResolutionEnabled(nativeType, nativeIndex)) {
            ALOGI("Choosing sink's native resolution");
            *chosenType = nativeType;
            *chosenIndex = nativeIndex;
            return true;
        }
    } else {
        ALOGW("Sink advertised native resolution that it doesn't "
              "actually support... ignoring");
    }

    sourceSupported.getNativeResolution(&nativeType, &nativeIndex);
    if (sourceSupported.isResolutionEnabled(nativeType, nativeIndex)) {
        if (sinkSupported.isResolutionEnabled(nativeType, nativeIndex)) {
            ALOGI("Choosing source's native resolution");
            *chosenType = nativeType;
            *chosenIndex = nativeIndex;
            return true;
        }
    } else {
        ALOGW("Source advertised native resolution that it doesn't "
              "actually support... ignoring");
    }
#endif

    bool first = true;
    uint32_t bestScore = 0;
    size_t bestType = 0;
    size_t bestIndex = 0;
    for (size_t i = 0; i < kNumResolutionTypes; ++i) {
        for (size_t j = 0; j < 32; ++j) {
            size_t width, height, framesPerSecond;
            bool interlaced;
            if (!GetConfiguration(
                        (ResolutionType)i,
                        j,
                        &width, &height, &framesPerSecond, &interlaced)) {
                break;
            }

            if (!sinkSupported.isResolutionEnabled((ResolutionType)i, j)
                    || !sourceSupported.isResolutionEnabled(
                        (ResolutionType)i, j)) {
                continue;
            }

            ALOGV("type %zu, index %zu, %zu x %zu %c%zu supported",
                  i, j, width, height, interlaced ? 'i' : 'p', framesPerSecond);

            uint32_t score = width * height * framesPerSecond;
            if (!interlaced) {
                score *= 2;
            }

            if (first || score > bestScore) {
                bestScore = score;
                bestType = i;
                bestIndex = j;

                first = false;
            }
        }
    }

    if (first) {
        return false;
    }

    *chosenType = (ResolutionType)bestType;
    *chosenIndex = bestIndex;

    // Pick the best profile/level supported by both sink and source.
    ProfileType srcProfile, sinkProfile;
    LevelType srcLevel, sinkLevel;
    sourceSupported.getProfileLevel(
                        (ResolutionType)bestType, bestIndex,
                        &srcProfile, &srcLevel);
    sinkSupported.getProfileLevel(
                        (ResolutionType)bestType, bestIndex,
                        &sinkProfile, &sinkLevel);
    *chosenProfile = srcProfile < sinkProfile ? srcProfile : sinkProfile;
    *chosenLevel = srcLevel < sinkLevel ? srcLevel : sinkLevel;

    return true;
}

}  // namespace android

