// Copyright (c) 2018-2019 Intel Corporation
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.


#include "umc_defs.h"

#if defined MFX_ENABLE_MPEG2_VIDEO_DECODE

#include "mfxstructures.h"
#include "umc_video_decoder.h"

#include "mfx_common_int.h"
#include "umc_mpeg2_utils.h"

#define CHECK_UNSUPPORTED(EXPR) if (EXPR) return MFX_ERR_UNSUPPORTED;

namespace UMC_MPEG2_DECODER
{
    // Check HW capabilities
    bool IsNeedPartialAcceleration_MPEG2(mfxVideoParam* par)
    {
        return (par->mfx.CodecProfile == MFX_PROFILE_MPEG1) ? true : false;
    }

    // Returns implementation platform
    eMFXPlatform GetPlatform_MPEG2(VideoCORE * core, mfxVideoParam * par)
    {
        if (!par)
            return MFX_PLATFORM_SOFTWARE;

        eMFXPlatform platform = core->GetPlatformType();

        if (IsNeedPartialAcceleration_MPEG2(par) && platform != MFX_PLATFORM_SOFTWARE)
        {
            return MFX_PLATFORM_SOFTWARE;
        }

        if (platform != MFX_PLATFORM_SOFTWARE)
        {
            if (MFX_ERR_NONE != core->IsGuidSupported(sDXVA2_ModeMPEG2_VLD, par))
            {
                platform = MFX_PLATFORM_SOFTWARE;
            }
        }

        return platform;
    }

    bool IsHWSupported(VideoCORE *core, mfxVideoParam *par)
    {
        if ((par->mfx.CodecProfile == MFX_PROFILE_MPEG1 && core->GetPlatformType()== MFX_PLATFORM_HARDWARE))
            return false;

        if (MFX_ERR_NONE != core->IsGuidSupported(sDXVA2_ModeMPEG2_VLD, par))
            return false;

        return true;
    }

    mfxStatus CheckFrameInfo(mfxFrameInfo & in, mfxFrameInfo & out)
    {
        mfxStatus sts = MFX_ERR_NONE;

        if (!((in.Width % 16 == 0) && (in.Width <= 4096)))
        {
            out.Width = 0;
            sts = MFX_ERR_UNSUPPORTED;
        }

        if (!((in.Height % 16 == 0) && (in.Height <= 4096)))
        {
            out.Height = 0;
            sts = MFX_ERR_UNSUPPORTED;
        }

        if (!(in.CropX <= out.Width))
        {
            out.CropX = 0;
            sts = MFX_ERR_UNSUPPORTED;
        }

        if (!(in.CropY <= out.Height))
        {
            out.CropY = 0;
            sts = MFX_ERR_UNSUPPORTED;
        }

        if (!(out.CropX + in.CropW <= out.Width))
        {
            out.CropW = 0;
            sts = MFX_ERR_UNSUPPORTED;
        }

        if (!(out.CropY + in.CropH <= out.Height))
        {
            out.CropH = 0;
            sts = MFX_ERR_UNSUPPORTED;
        }

        if (!(in.PicStruct == MFX_PICSTRUCT_PROGRESSIVE ||
            in.PicStruct == MFX_PICSTRUCT_FIELD_TFF ||
            in.PicStruct == MFX_PICSTRUCT_FIELD_BFF ||
            in.PicStruct == MFX_PICSTRUCT_UNKNOWN))
        {
            out.PicStruct = 0;
            sts = MFX_ERR_UNSUPPORTED;
        }

        if (!(in.ChromaFormat == MFX_CHROMAFORMAT_YUV420 || in.ChromaFormat == 0))
        {
            out.ChromaFormat = 0;
            sts = MFX_ERR_UNSUPPORTED;
        }
        return sts;
    }

    // MediaSDK API Query function
    mfxStatus Query(VideoCORE *core, mfxVideoParam *in, mfxVideoParam *out)
    {
        MFX_CHECK_NULL_PTR1(out);
        mfxStatus res = MFX_ERR_NONE;

        eMFXHWType type = core->GetHWType();
        if (!in)
        {
            // configurability mode
            memset(out, 0, sizeof(*out));
            out->mfx.CodecId = MFX_CODEC_MPEG2;
            out->mfx.FrameInfo.FourCC = 1;
            out->mfx.FrameInfo.Width = 1;
            out->mfx.FrameInfo.Height = 1;
            out->mfx.FrameInfo.CropX = 1;
            out->mfx.FrameInfo.CropY = 1;
            out->mfx.FrameInfo.CropW = 1;
            out->mfx.FrameInfo.CropH = 1;
            out->mfx.FrameInfo.AspectRatioH = 1;
            out->mfx.FrameInfo.AspectRatioW = 1;

            out->mfx.FrameInfo.FrameRateExtN = 1;
            out->mfx.FrameInfo.FrameRateExtD = 1;
            out->mfx.FrameInfo.PicStruct = 1;
            out->mfx.CodecProfile = 1;
            out->mfx.CodecLevel = 1;
            out->mfx.ExtendedPicStruct = 1;
            out->mfx.TimeStampCalc = 1;

            out->Protected = 0;

            out->mfx.FrameInfo.ChromaFormat = MFX_CHROMAFORMAT_YUV420;

            out->IOPattern = type == (mfxU16)(MFX_HW_UNKNOWN ? MFX_IOPATTERN_OUT_SYSTEM_MEMORY : MFX_IOPATTERN_OUT_VIDEO_MEMORY);

            // DECODE's configurables
            out->mfx.NumThread = 1;
            out->AsyncDepth = MFX_AUTO_ASYNC_DEPTH_VALUE;
        }
        else
        {
            // checking mode
            CHECK_UNSUPPORTED(1 == in->mfx.DecodedOrder);

            if (in->mfx.FrameInfo.FourCC != MFX_FOURCC_NV12)
            {
                out->mfx.FrameInfo.FourCC = 0;
                MFX_RETURN(MFX_ERR_UNSUPPORTED);
            }

            CHECK_UNSUPPORTED( (in->NumExtParam == 0) != (in->ExtParam == nullptr) );

            mfxExtBuffer **pExtBuffer = out->ExtParam;
            mfxU16 numDistBuf = out->NumExtParam;
            *out = *in;

            out->AsyncDepth = in->AsyncDepth == 0 ? MFX_AUTO_ASYNC_DEPTH_VALUE : out->AsyncDepth;
            out->ExtParam = pExtBuffer;
            out->NumExtParam = numDistBuf;

            res = CheckFrameInfo(in->mfx.FrameInfo, out->mfx.FrameInfo);
            MFX_CHECK_STS(res);

            out->mfx.CodecId = in->mfx.CodecId != MFX_CODEC_MPEG2 ? 0 : out->mfx.CodecId;

            if (!(in->mfx.CodecLevel == MFX_LEVEL_MPEG2_LOW ||
                in->mfx.CodecLevel == MFX_LEVEL_MPEG2_MAIN ||
                in->mfx.CodecLevel == MFX_LEVEL_MPEG2_HIGH1440 ||
                in->mfx.CodecLevel == MFX_LEVEL_MPEG2_HIGH ||
                in->mfx.CodecLevel == 0))
            {
                out->mfx.CodecLevel = 0;
                MFX_RETURN(MFX_ERR_UNSUPPORTED);
            }

            if (!(in->mfx.CodecProfile == MFX_PROFILE_MPEG2_SIMPLE ||
                in->mfx.CodecProfile == MFX_PROFILE_MPEG2_MAIN ||
                in->mfx.CodecProfile == MFX_PROFILE_MPEG2_HIGH ||
                in->mfx.CodecProfile == MFX_PROFILE_MPEG1 ||
                in->mfx.CodecProfile == 0))
            {
                out->mfx.CodecProfile = 0;
                MFX_RETURN(MFX_ERR_UNSUPPORTED);
            }

            if (in->IOPattern)
            {
                if ((in->IOPattern == MFX_IOPATTERN_OUT_SYSTEM_MEMORY) || (in->IOPattern == MFX_IOPATTERN_OUT_VIDEO_MEMORY))
                    out->IOPattern = in->IOPattern;
                else
                    MFX_RETURN(MFX_ERR_UNSUPPORTED);
            }

            CHECK_UNSUPPORTED(!IsHWSupported(core, in));
        }

        return res;
    }

    // Table 6-4 - frame_rate_value
    void GetMfxFrameRate(uint8_t frame_rate_value, mfxU32 & frameRateN, mfxU32 & frameRateD)
    {
        switch (frame_rate_value)
        {
            case 0:  frameRateN = 30;    frameRateD = 1;    break;
            case 1:  frameRateN = 24000; frameRateD = 1001; break;
            case 2:  frameRateN = 24;    frameRateD = 1;    break;
            case 3:  frameRateN = 25;    frameRateD = 1;    break;
            case 4:  frameRateN = 30000; frameRateD = 1001; break;
            case 5:  frameRateN = 30;    frameRateD = 1;    break;
            case 6:  frameRateN = 50;    frameRateD = 1;    break;
            case 7:  frameRateN = 60000; frameRateD = 1001; break;
            case 8:  frameRateN = 60;    frameRateD = 1;    break;
            default: frameRateN = 0;     frameRateD = 0;
        }
        return;
    }

    // Table 8-2 - Profile identification
    mfxU8 GetMfxCodecProfile(uint8_t profile)
    {
        switch (profile)
        {
            case 5:
                return MFX_PROFILE_MPEG2_SIMPLE;
            case 4:
                return MFX_PROFILE_MPEG2_MAIN;
            case 1:
                return MFX_PROFILE_MPEG2_HIGH;
            default:
                return MFX_PROFILE_UNKNOWN;
        }
    }

    // Table 8-3 - Level identification
    mfxU8 GetMfxCodecLevel(uint8_t level)
    {
        switch (level)
        {
            case 10:
                return MFX_LEVEL_MPEG2_LOW;
            case 8:
                return MFX_LEVEL_MPEG2_MAIN;
            case 6:
                return MFX_LEVEL_MPEG2_HIGH1440;
            case 4:
                return MFX_LEVEL_MPEG2_HIGH;
            default:
                return MFX_LEVEL_UNKNOWN;
        }
    }

    void DARtoPAR(uint32_t width, uint32_t height, uint32_t dar_h, uint32_t dar_v,
                uint16_t & par_h, uint16_t &par_v)
    {
        uint32_t simple_tab[] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59};

        uint32_t h = dar_h * height;
        uint32_t v = dar_v * width;

        // remove common multipliers
        while ( ((h|v)&1) == 0 )
        {
            h >>= 1;
            v >>= 1;
        }

        uint32_t denom;
        for (uint32_t i=0; i < (uint32_t)(sizeof(simple_tab)/sizeof(simple_tab[0])); ++i)
        {
            denom = simple_tab[i];
            while(h%denom==0 && v%denom==0)
            {
                v /= denom;
                h /= denom;
            }
            if (v <= denom || h <= denom)
                break;
        }
        par_h = (uint16_t)h;
        par_v = (uint16_t)v;
    }

    void CalcAspectRatio(uint32_t dar, uint32_t width, uint32_t height,
                        uint16_t & aspectRatioW, uint16_t & aspectRatioH)
    {
        if (dar == 2)
        {
            DARtoPAR(width, height, 4, 3, aspectRatioW, aspectRatioH);
        }
        else if (dar == 3)
        {
            DARtoPAR(width, height, 16, 9, aspectRatioW, aspectRatioH);
        }
        else if (dar == 4)
        {
            DARtoPAR(width, height, 221, 100, aspectRatioW, aspectRatioH);
        }
        else // dar == 1 or unknown
        {
            aspectRatioW = 1;
            aspectRatioH = 1;
        }
    }

    // Validate input parameters
    bool CheckVideoParam(mfxVideoParam *in)
    {
        if (!in)
            return false;

        if (MFX_CODEC_MPEG2 != in->mfx.CodecId)
            return false;

        if (in->mfx.FrameInfo.FourCC != MFX_FOURCC_NV12)
            return false;

        // both zero or not zero
        if ((in->mfx.FrameInfo.AspectRatioW || in->mfx.FrameInfo.AspectRatioH) && !(in->mfx.FrameInfo.AspectRatioW && in->mfx.FrameInfo.AspectRatioH))
            return false;

        if ((MFX_PROFILE_UNKNOWN      != in->mfx.CodecProfile) &&
            (MFX_PROFILE_MPEG2_SIMPLE != in->mfx.CodecProfile) &&
            (MFX_PROFILE_MPEG2_MAIN   != in->mfx.CodecProfile) &&
            (MFX_PROFILE_MPEG2_HIGH   != in->mfx.CodecProfile))
            return false;

        switch (in->mfx.FrameInfo.PicStruct)
        {
        case MFX_PICSTRUCT_UNKNOWN:
        case MFX_PICSTRUCT_PROGRESSIVE:
        case MFX_PICSTRUCT_FIELD_TFF:
        case MFX_PICSTRUCT_FIELD_BFF:
            break;
        default:
            return false;
        }

        if (in->mfx.FrameInfo.ChromaFormat != MFX_CHROMAFORMAT_YUV420)
            return false;

        if (   !(in->IOPattern & MFX_IOPATTERN_OUT_VIDEO_MEMORY)
            && !(in->IOPattern & MFX_IOPATTERN_OUT_SYSTEM_MEMORY)
            )
            return false;

        if ((in->IOPattern & MFX_IOPATTERN_OUT_VIDEO_MEMORY) && (in->IOPattern & MFX_IOPATTERN_OUT_SYSTEM_MEMORY))
            return false;

        return true;
    }

    // Initialize mfxVideoParam structure based on decoded bitstream header values
    UMC::Status UMC_MPEG2_DECODER::MFX_Utility::FillVideoParam(const MPEG2SequenceHeader & seq,
                                                               const MPEG2SequenceExtension * seqExt,
                                                               const MPEG2SequenceDisplayExtension * dispExt,
                                                               mfxVideoParam & par)
    {
        par.mfx.CodecId = MFX_CODEC_MPEG2;

        par.mfx.FrameInfo.PicStruct = (mfxU16)(seqExt && seqExt->progressive_sequence ? MFX_PICSTRUCT_PROGRESSIVE : MFX_PICSTRUCT_UNKNOWN);

        par.mfx.FrameInfo.CropX = 0;
        par.mfx.FrameInfo.CropY = 0;
        par.mfx.FrameInfo.CropW = (mfxU16)seq.horizontal_size_value;
        par.mfx.FrameInfo.CropH = (mfxU16)seq.vertical_size_value;

        par.mfx.FrameInfo.Width = mfx::align2_value<mfxU16>(par.mfx.FrameInfo.CropW, 16);
        par.mfx.FrameInfo.Height = mfx::align2_value<mfxU16>(par.mfx.FrameInfo.CropH, (MFX_PICSTRUCT_PROGRESSIVE == par.mfx.FrameInfo.PicStruct) ? 16 : 32);

        par.mfx.FrameInfo.BitDepthLuma   = 8;
        par.mfx.FrameInfo.BitDepthChroma = 8;
        par.mfx.FrameInfo.Shift = 0;

        if (seqExt)
        {
            par.mfx.FrameInfo.ChromaFormat = (mfxU16)(seqExt->chroma_format == CHROMA_FORMAT_420 ?
                                                MFX_CHROMAFORMAT_YUV420 :
                                                (seqExt->chroma_format == CHROMA_FORMAT_422 ?
                                                        MFX_CHROMAFORMAT_YUV422 :
                                                        MFX_CHROMAFORMAT_YUV444));

            // Table 8-1 - Meaning of bits in profile_and_level_indication
            par.mfx.CodecProfile = GetMfxCodecProfile((seqExt->profile_and_level_indication >> 4) & 7); // [6:4] bits
            par.mfx.CodecLevel = GetMfxCodecLevel(seqExt->profile_and_level_indication & 0xF); // [3:0] bits
        }
        else
        {
            par.mfx.FrameInfo.ChromaFormat = MFX_CHROMAFORMAT_YUV420;
            par.mfx.CodecProfile = MFX_PROFILE_MPEG1;
            par.mfx.CodecLevel = MFX_LEVEL_UNKNOWN;
        }

        // 6.3.3
        uint32_t w = dispExt ? dispExt->display_horizontal_size : seq.horizontal_size_value;
        uint32_t h = dispExt ? dispExt->display_vertical_size : seq.vertical_size_value;
        CalcAspectRatio(seq.aspect_ratio_information, w, h, par.mfx.FrameInfo.AspectRatioW, par.mfx.FrameInfo.AspectRatioH);

        GetMfxFrameRate((uint8_t)seq.frame_rate_code, par.mfx.FrameInfo.FrameRateExtN, par.mfx.FrameInfo.FrameRateExtD);

        par.mfx.FrameInfo.FourCC = MFX_FOURCC_NV12;

        // video signal section
        mfxExtVideoSignalInfo * videoSignal = (mfxExtVideoSignalInfo *)GetExtendedBuffer(par.ExtParam, par.NumExtParam, MFX_EXTBUFF_VIDEO_SIGNAL_INFO);
        if (!videoSignal)
            return UMC::UMC_OK;

        if (dispExt)
        {
            videoSignal->VideoFormat              = (mfxU16)dispExt->video_format;
            videoSignal->ColourPrimaries          = (mfxU16)dispExt->colour_primaries;
            videoSignal->TransferCharacteristics  = (mfxU16)dispExt->transfer_characteristics;
            videoSignal->MatrixCoefficients       = (mfxU16)dispExt->matrix_coefficients;
            videoSignal->ColourDescriptionPresent = (mfxU16)dispExt->colour_description;
        }
        else
        {
            // default values
            videoSignal->VideoFormat              = 5; // unspecified video format
            videoSignal->ColourPrimaries          = 1;
            videoSignal->TransferCharacteristics  = 1;
            videoSignal->MatrixCoefficients       = 1;
            videoSignal->ColourDescriptionPresent = 0;
        }

        return UMC::UMC_OK;
    }
}
#endif // MFX_ENABLE_MPEG2_VIDEO_DECODE
