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

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "common/log.h"
#include "common/nalreader.h"
#include "vaapidecpicture.h"
#include "vaapidecoder_mpeg2.h"

#include <string.h>
#include <inttypes.h>

using YamiParser::MPEG2::Slice;
using YamiParser::MPEG2::PictureCodingExtension;
using YamiParser::MPEG2::PictureHeader;
using YamiParser::MPEG2::GOPHeader;
using YamiParser::MPEG2::SeqExtension;
using YamiParser::MPEG2::SeqHeader;
using YamiParser::MPEG2::StreamHeader;
using YamiParser::MPEG2::Parser;
using YamiParser::MPEG2::QuantMatrices;

namespace YamiMediaCodec {
typedef VaapiDecoderMPEG2::PicturePtr PicturePtr;

IQMatricesRefs::IQMatricesRefs() { memset(this, 0, sizeof(*this)); }
// mpeg2picture class

class VaapiDecPictureMpeg2 : public VaapiDecPicture {
public:
    VaapiDecPictureMpeg2(const ContextPtr& context, const SurfacePtr& surface,
                         int64_t timeStamp)
        : VaapiDecPicture(context, surface, timeStamp)
        , m_VAPictureStructure_(VAAPI_PICTURE_FRAME)
        , m_sentToOutput_(false)
        , m_isFirstField_(false)
    {
    }
    VaapiDecPictureMpeg2() {}

    // move to private and create get/set methods
    VaapiPictureType m_VAPictureStructure_;
    bool m_sentToOutput_;
    uint32_t m_temporalReference_;
    uint32_t m_pictureCodingType_;
    uint32_t m_topFieldFirst_;
    uint32_t m_progressiveFrame_;
    bool m_isFirstField_;

private:
};
// mpeg2picture class

void VaapiDecoderMPEG2::DPB::flush()
{
    DEBUG("MPEG2-DPB flush");
    m_referencePictures.clear();
}

YamiStatus
VaapiDecoderMPEG2::DPB::outputPreviousPictures(const PicturePtr& picture, bool empty)
{
    YamiStatus status = YAMI_SUCCESS;
    std::list<PicturePtr>::iterator it = m_referencePictures.begin();

    if (picture->m_pictureCodingType_ == YamiParser::MPEG2::kIFrame)
        empty = true;

    // send to the output all possible pictures
    for (; it != m_referencePictures.end(); it++) {
        DEBUG("candidate picture temporalReference %d",
              picture->m_temporalReference_);

        if ((picture->m_temporalReference_ >= (*it)->m_temporalReference_
             || empty)
            && (*it)->m_sentToOutput_ == false) {

            status = callOutputPicture((*it));
            if (status != YAMI_SUCCESS)
                return status;

            (*it)->m_sentToOutput_ = true;
            DEBUG("output temporalReference %d", (*it)->m_temporalReference_);
        }
    }

    return status;
}

YamiStatus
VaapiDecoderMPEG2::DPB::insertPictureToReferences(const PicturePtr& picture)
{
    PicturePtr outputPicture;
    YamiStatus status = YAMI_SUCCESS;

    if (picture->m_pictureCodingType_ != YamiParser::MPEG2::kBFrame)
    {
        DEBUG("Put picture %d on the reference queue",
              picture->m_temporalReference_);

        if (m_referencePictures.size() == 2) {
            outputPicture = m_referencePictures.front();
            DEBUG("Drop picture %d from reference queue",
                  outputPicture->m_temporalReference_);
            m_referencePictures.pop_front();
        }
        m_referencePictures.push_back(picture);
    } else {
        // this is a kBFrame
        // send it to output right away
        DEBUG("Send B frame picture to the output %d",
              picture->m_temporalReference_);
        status = callOutputPicture(picture);
        if (status != YAMI_SUCCESS)
            return status;
    }
    return status;
}

YamiStatus VaapiDecoderMPEG2::DPB::insertPicture(const PicturePtr& picture)
{
    INFO("insertPicture to DPB size %zu", m_referencePictures.size());

    YamiStatus status = YAMI_SUCCESS;

    DEBUG("temporalReference %d", picture->m_temporalReference_);
    DEBUG("pictureCodingType %d", picture->m_pictureCodingType_);
    DEBUG("topFieldFirst %d", picture->m_topFieldFirst_);
    DEBUG("progressiveFrame %d", picture->m_progressiveFrame_);

    status = outputPreviousPictures(picture);
    if (status != YAMI_SUCCESS)
        return status;

    if (picture->m_progressiveFrame_
        || picture->m_VAPictureStructure_ == VAAPI_PICTURE_FRAME) {

        INFO("Insert one frame picture");
        // frame picture can be inserted straight to DPB as it was decoded
        // completely before trying to insert, it can also be
        // output if temporalReference matches PTS

        status = insertPictureToReferences(picture);
        if (status != YAMI_SUCCESS)
            return status;

    } else if (!picture->m_progressiveFrame_
               && picture->m_VAPictureStructure_
                  != VAAPI_PICTURE_FRAME) {
        // field pictures received, top field and bottom field have their lists
        // based on those a decision will be made if frame can be displayed
        INFO("This is a field picture");
        if (!picture->m_isFirstField_) {
            status = insertPictureToReferences(picture);
            if (status != YAMI_SUCCESS)
                return status;
        }
    }

    DEBUG("insertPicture returns dpb size %zu", m_referencePictures.size());
    return status;
}

YamiStatus
VaapiDecoderMPEG2::DPB::getReferencePictures(const PicturePtr& current_picture,
    PicturePtr& previousPicture,
    PicturePtr& nextPicture)
{
    // m_referencePictures can have 0,1 or 2 reference pictures to comply with
    // the amount of reference frames needed by picture coding type
    // picture coding type kIFrame - 0 ref frames
    // picture coding type kPFrame - 1 ref frames
    // picture coding type kBFrame - 2 ref frames

    if (m_referencePictures.size() == 1) {
        previousPicture = m_referencePictures.front();
    } else if (m_referencePictures.size() == 2
               && current_picture->m_pictureCodingType_
                  == YamiParser::MPEG2::kBFrame) {
        previousPicture = m_referencePictures.front();
        nextPicture = m_referencePictures.back();
    } else if (m_referencePictures.size() == 2
               && current_picture->m_pictureCodingType_
                  == YamiParser::MPEG2::kPFrame) {
        previousPicture = m_referencePictures.back();
    }

    return YAMI_SUCCESS;
}

VaapiDecoderMPEG2::VaapiDecoderMPEG2()
    : m_DPB(std::bind(&VaapiDecoderMPEG2::outputPicture, this,
                                 std::placeholders::_1))
    , m_VAStart(false)
    , m_isParsingSlices(false)
    , m_loadNewIQMatrix(false)
{
    m_parser.reset(new Parser());
    m_stream.reset(new StreamHeader());
    m_previousStartCode = YamiParser::MPEG2::MPEG2_INVALID_START_CODE;
    m_nextStartCode = YamiParser::MPEG2::MPEG2_SEQUENCE_HEADER_CODE;
    INFO("VaapiDecoderMPEG2 constructor");
}

VaapiDecoderMPEG2::~VaapiDecoderMPEG2() { stop(); }

YamiStatus VaapiDecoderMPEG2::convertToVAProfile(
    const YamiParser::MPEG2::ProfileType& profile)
{
    YamiStatus status = YAMI_SUCCESS;

    switch (profile) {
    case YamiParser::MPEG2::MPEG2_PROFILE_MAIN:
        m_VAProfile = VAProfileMPEG2Main;
        break;
    case YamiParser::MPEG2::MPEG2_PROFILE_SIMPLE:
        m_VAProfile = VAProfileMPEG2Simple;
        break;
    default:
        m_VAProfile = VAProfileNone;
        status = YAMI_DECODE_PARSER_FAIL;
        break;
    }
    return status;
}

YamiStatus
VaapiDecoderMPEG2::checkLevel(const YamiParser::MPEG2::LevelType& level)
{
    switch (level) {
    case YamiParser::MPEG2::MPEG2_LEVEL_HIGH:
    case YamiParser::MPEG2::MPEG2_LEVEL_HIGH_1440:
    case YamiParser::MPEG2::MPEG2_LEVEL_MAIN:
    case YamiParser::MPEG2::MPEG2_LEVEL_LOW:
        return YAMI_SUCCESS;
        break;
    default:
        return YAMI_DECODE_PARSER_FAIL;
        break;
    }
}

YamiStatus VaapiDecoderMPEG2::start(VideoConfigBuffer* buffer)
{

    if (!buffer) {
        ERROR("Cannot start codec without config buffer");
        return YAMI_FAIL;
    }

    m_configBuffer = *buffer;

    return YAMI_SUCCESS;
}

YamiStatus VaapiDecoderMPEG2::processConfigBuffer()
{
    // buffer can contain one startcode of the mpeg2 sequence
    // or several, but they will be processed one by one

    YamiStatus status = YAMI_SUCCESS;
    YamiParser::MPEG2::StartCodeType next_code;
    YamiParser::MPEG2::ExtensionIdentifierType extID;

    m_parser->nextStartCode(m_stream.get(), next_code);
    INFO("processConfigBuffer Next start_code %x", next_code);

    switch (next_code) {
    case YamiParser::MPEG2::MPEG2_SEQUENCE_HEADER_CODE:
        INFO("parseSeqHdr");
        if (!m_parser->parseSequenceHeader(m_stream.get())) {
            return YAMI_DECODE_PARSER_FAIL;
        }
        m_sequenceHeader = m_parser->getSequenceHeader();
        m_previousStartCode = m_nextStartCode;
        m_nextStartCode = YamiParser::MPEG2::MPEG2_EXTENSION_START_CODE;
        m_loadNewIQMatrix
            = updateIQMatrix(&m_sequenceHeader->quantizationMatrices, true);
        if (!m_DPB.isEmpty())
            m_DPB.flush();
        break;
    case YamiParser::MPEG2::MPEG2_EXTENSION_START_CODE:
        INFO("parseSeqExt");
        extID = static_cast<YamiParser::MPEG2::ExtensionIdentifierType>(
            m_stream->nalData[1] >> 4);
        if (!m_VAStart) {

            switch (extID) {
            case YamiParser::MPEG2::kSequence:
                if (!m_parser->parseSequenceExtension(m_stream.get())) {
                    return YAMI_DECODE_PARSER_FAIL;
                }
                m_sequenceExtension = m_parser->getSequenceExtension();
                m_previousStartCode = m_nextStartCode;
                m_nextStartCode = YamiParser::MPEG2::MPEG2_GROUP_START_CODE;
                // ready to fillConfigBuffer
                // this should be returning YAMI_SUCCESS if
                // application
                // has not allocated output port earlier

                status = fillConfigBuffer();
                if (status != YAMI_SUCCESS) {
                    return status;
                }
                break;

            case YamiParser::MPEG2::kQuantizationMatrix:
                if (!m_parser->parseQuantMatrixExtension(m_stream.get())) {
                    return YAMI_DECODE_PARSER_FAIL;
                }
                m_quantMatrixExtension = m_parser->getQuantMatrixExtension();
                m_loadNewIQMatrix = updateIQMatrix(
                    &m_quantMatrixExtension->quantizationMatrices);
                break;
            default:
                break;
            }
        }
        break;
    default:
        status = processDecodeBuffer();
        INFO("Calling processDecodeBuffer from "
             "processConfigBuffer");
        if (status != YAMI_SUCCESS) {
            return status;
        }
        break;
    }
    return status;
}

YamiStatus VaapiDecoderMPEG2::processDecodeBuffer()
{
    // buffer can contain only one nal of the mpeg2 sequence

    YamiStatus status = YAMI_SUCCESS;
    YamiParser::MPEG2::StartCodeType next_code;
    YamiParser::MPEG2::ExtensionIdentifierType extID;

    m_parser->nextStartCode(m_stream.get(), next_code);
    DEBUG("processDecodeBuffer Next start_code %x", next_code);

    switch (next_code) {
    case YamiParser::MPEG2::MPEG2_SEQUENCE_HEADER_CODE:
        INFO("parseSeqHdr");
        if (!m_parser->parseSequenceHeader(m_stream.get())) {
            return YAMI_DECODE_PARSER_FAIL;
        }
        m_sequenceHeader = m_parser->getSequenceHeader();
        // reset the matrices
        m_previousStartCode = m_nextStartCode;
        m_nextStartCode = YamiParser::MPEG2::MPEG2_EXTENSION_START_CODE;
        m_loadNewIQMatrix
            = updateIQMatrix(&m_sequenceHeader->quantizationMatrices, true);

        break;
    case YamiParser::MPEG2::MPEG2_GROUP_START_CODE:
        INFO("Group start code");

        if (m_isParsingSlices) {
            status = decodePicture();
            if (status != YAMI_SUCCESS) {
                return status;
            }
            m_isParsingSlices = false;
            m_nextStartCode = YamiParser::MPEG2::MPEG2_GROUP_START_CODE;
        }

        if (!m_parser->parseGOPHeader(m_stream.get())) {
            return YAMI_DECODE_PARSER_FAIL;
        }
        m_GOPHeader = m_parser->getGOPHeader();
        m_previousStartCode = m_nextStartCode;
        m_nextStartCode = YamiParser::MPEG2::MPEG2_PICTURE_START_CODE;
        break;
    case YamiParser::MPEG2::MPEG2_PICTURE_START_CODE:
        INFO("picture start code");
        if (m_isParsingSlices) {
            status = decodePicture();
            if (status != YAMI_SUCCESS) {
                return status;
            }
            m_isParsingSlices = false;
        }

        if (m_previousStartCode
            == YamiParser::MPEG2::MPEG2_EXTENSION_START_CODE
            || m_previousStartCode
            == YamiParser::MPEG2::MPEG2_GROUP_START_CODE) {
            m_nextStartCode = YamiParser::MPEG2::MPEG2_PICTURE_START_CODE;
            INFO("parsePictureHeader size %ld", m_stream->streamSize);
            if (!m_parser->parsePictureHeader(m_stream.get())) {
                return YAMI_DECODE_PARSER_FAIL;
            }
            m_pictureHeader = m_parser->getPictureHeader();
            m_previousStartCode = m_nextStartCode;
            m_nextStartCode = YamiParser::MPEG2::MPEG2_EXTENSION_START_CODE;
        }
        break;
    case YamiParser::MPEG2::MPEG2_EXTENSION_START_CODE:
        extID = static_cast<YamiParser::MPEG2::ExtensionIdentifierType>(
            m_stream->nalData[1] >> 4);
        if (extID == YamiParser::MPEG2::kPictureCoding) {
            if (m_nextStartCode == next_code) {
                if (!m_parser->parsePictureCodingExtension(m_stream.get())) {
                    return YAMI_DECODE_PARSER_FAIL;
                }
                m_pictureCodingExtension
                    = m_parser->getPictureCodingExtension();

                // picture can be created as soon as the first slice is
                // parsed, but from here to there Extension Start Code can be
                // parsed to update information like the Quant Matrix

                m_canCreatePicture = true;
                m_previousStartCode = m_nextStartCode;
                m_nextStartCode = YamiParser::MPEG2::MPEG2_SLICE_START_CODE_MIN;
                m_isParsingSlices = true;
            }
        }
        else if (extID == YamiParser::MPEG2::kQuantizationMatrix) {
            INFO("this is a new quant matrix");
            if (!m_parser->parseQuantMatrixExtension(m_stream.get())) {
                return YAMI_DECODE_PARSER_FAIL;
            }
            m_quantMatrixExtension = m_parser->getQuantMatrixExtension();
            m_loadNewIQMatrix
                = updateIQMatrix(&m_quantMatrixExtension->quantizationMatrices);
        }
        else if (extID == YamiParser::MPEG2::kSequence) {
            if (!m_parser->parseSequenceExtension(m_stream.get())) {
                return YAMI_DECODE_PARSER_FAIL;
            }
            m_sequenceExtension = m_parser->getSequenceExtension();
            m_previousStartCode = m_nextStartCode;
            m_nextStartCode = YamiParser::MPEG2::MPEG2_GROUP_START_CODE;
            status = fillConfigBuffer();
            if (status != YAMI_SUCCESS) {
                return status;
            }
        }
        break;
    case YamiParser::MPEG2::MPEG2_SEQUENCE_END_CODE:
        INFO("End of sequence received");
        if (m_isParsingSlices) {
            status = decodePicture();
            if (status != YAMI_SUCCESS) {
                return status;
            }
            status = m_DPB.outputPreviousPictures(m_currentPicture, true);
            if (status != YAMI_SUCCESS) {
                return status;
            }
            m_isParsingSlices = false;
        }
        break;
    default:
        // process a SliceCode
        if (isSliceCode(next_code)) {

            if (m_canCreatePicture)
            {
                // create a new picture with updated information a field
                // picture doesn't need to create Picture twice as both top
                // and bottom fields share the same slice number.
                status = createPicture();
                if (status != YAMI_SUCCESS) {
                    return status;
                }
                m_canCreatePicture = false;
            }
            INFO("processSlice on next_code %x", next_code);
            status = processSlice();
            if (status != YAMI_SUCCESS) {
                return status;
            }
        }
        break;
    }
    return status;
}

YamiStatus VaapiDecoderMPEG2::fillConfigBuffer()
{

    YamiStatus status = YAMI_SUCCESS;
    YamiParser::MPEG2::ProfileType profile;
    YamiParser::MPEG2::LevelType level;

    profile = static_cast<YamiParser::MPEG2::ProfileType>(
        (m_sequenceExtension->profile_and_level_indication & 0x70) >> 4);
    level = static_cast<YamiParser::MPEG2::LevelType>(
        m_sequenceExtension->profile_and_level_indication & 0xF);

    status = checkLevel(level);
    if (status != YAMI_SUCCESS) {
        return status;
    }

    status = convertToVAProfile(profile);
    if (status != YAMI_SUCCESS) {
        return status;
    }

    m_configBuffer.width
        = (m_sequenceExtension->horizontal_size_extension & 0x3)
          | (m_sequenceHeader->horizontal_size_value & 0xFFF);
    m_configBuffer.height = (m_sequenceExtension->vertical_size_extension & 0x3)
                            | (m_sequenceHeader->vertical_size_value & 0xFFF);

    if (m_VAStart) {
        // sequence start extension code with VA started has to conduct a
        // port reconfiguration when stream size changes
        if (m_configBuffer.width > m_videoFormatInfo.width
            || m_configBuffer.height > m_videoFormatInfo.height) {
            // need to re-start VA to generate new surfaces
            status = VaapiDecoderBase::terminateVA();
            if (status != YAMI_SUCCESS) {
                return status;
            }
            m_VAStart = false;
        }
        else if (m_configBuffer.width != m_videoFormatInfo.width
                 || m_configBuffer.height != m_videoFormatInfo.height) {
            // VA surfaces can be re-used and client is notified
            // public member from base class
            m_videoFormatInfo.width = m_configBuffer.width;
            m_videoFormatInfo.height = m_configBuffer.height;
            m_videoFormatInfo.surfaceWidth = m_configBuffer.width;
            m_videoFormatInfo.surfaceHeight = m_configBuffer.height;
            m_configBuffer.surfaceWidth = m_configBuffer.width;
            m_configBuffer.surfaceHeight = m_configBuffer.height;
            status = YAMI_DECODE_FORMAT_CHANGE;
        }
    }

    if (!m_VAStart) {

        m_configBuffer.profile = m_VAProfile;

        DEBUG("MPEG2: start() buffer size: %d x %d m_VAProfile %x level %x",
              m_configBuffer.width, m_configBuffer.height,
              m_configBuffer.profile, level);

        m_configBuffer.surfaceWidth = m_configBuffer.width;
        m_configBuffer.surfaceHeight = m_configBuffer.height;
        if (!m_configBuffer.surfaceNumber) {
            m_configBuffer.surfaceNumber = kMinSurfaces;
        }
        // no information is sent to start libva at this point
        m_configBuffer.data = NULL;
        m_configBuffer.size = 0;

        // ready to start libva
        status = VaapiDecoderBase::start(&m_configBuffer);
        if (status != YAMI_SUCCESS) {
            return status;
        }
        m_VAStart = true;
        status = YAMI_DECODE_FORMAT_CHANGE;
    }

    return status;
}

bool VaapiDecoderMPEG2::isSliceCode(YamiParser::MPEG2::StartCodeType next_code)
{
    if (next_code >= YamiParser::MPEG2::MPEG2_SLICE_START_CODE_MIN
        && next_code <= YamiParser::MPEG2::MPEG2_SLICE_START_CODE_MAX) {
        DEBUG("Slice code slice number %x", next_code);
        return true;
    }

    return false;
}

YamiStatus VaapiDecoderMPEG2::processSlice()
{
    const Slice* slice;
    YamiStatus status = YAMI_DECODE_PARSER_FAIL;

    if (m_parser->parseSlice(m_stream.get())) {
        slice = m_parser->getMPEG2Slice();
        if (!m_currentPicture->newSlice(mpeg2SliceParams,
                slice->sliceData, slice->sliceDataSize)) {
            DEBUG("picture->newSlice failed");
            return YAMI_FAIL;
        }
        fillSliceParams(mpeg2SliceParams, slice);
        status = YAMI_SUCCESS;
    }

    return status;
}

inline bool findTempReference(const PicturePtr& picture,
                              uint32_t temporalReference)
{
    return picture->m_temporalReference_ == temporalReference;
}

YamiStatus VaapiDecoderMPEG2::assignSurface()
{
    SurfacePtr surface;
    YamiStatus status = YAMI_SUCCESS;

    surface = VaapiDecoderBase::createSurface();

    if (!surface) {
        status = YAMI_DECODE_NO_SURFACE;
    } else {
        surface->setCrop(0, 0, m_configBuffer.width, m_configBuffer.height);
        m_currentPicture.reset(
            new VaapiDecPictureMpeg2(m_context, surface, m_currentPTS));
        m_currentPicture->m_isFirstField_ = true;
    }

    return status;
}

YamiStatus VaapiDecoderMPEG2::findReusePicture(std::list<PicturePtr>& list,
    bool& reuse)
{
    std::list<PicturePtr>::iterator it;
    it = std::find_if(list.begin(), list.end(),
                      std::bind(findTempReference,
                                     std::placeholders::_1,
                                     m_pictureHeader->temporal_reference));
    if (it != list.end()) {
        m_currentPicture = (*it);
        m_currentPicture->m_isFirstField_ = false;
        list.erase(it);
        reuse = true;
    }
    return YAMI_SUCCESS;
}

YamiStatus VaapiDecoderMPEG2::assignPicture()
{
    YamiStatus status = YAMI_SUCCESS;
    bool reuseSurface = false;

    // reuse surface/picture when decoding a field picture, only complete
    // pictures can be pushed to the dpb, so it is up to the topfield and
    // bottomfield lists to hold  partial pictures before pushing

    // determine if the current Picture is a field picture and if it is
    // topfield or bottomfield

    if (!m_pictureCodingExtension->progressive_frame
        && m_pictureCodingExtension->picture_structure != kFramePicture) {
        DEBUG("Create a Field Picture");
        // this is a field picture
        //
        if (m_pictureCodingExtension->top_field_first) {
            // top field picture first
            DEBUG("Create Top Field Picture");

            status = assignSurface();
            if (status != YAMI_SUCCESS) {
                return status;
            }

            m_topFieldPictures.push_back(m_currentPicture);

        } else {
            // bottom field picture
            if (!m_bottomFieldPictures.empty()) {
                findReusePicture(m_bottomFieldPictures, reuseSurface);
                if (status != YAMI_SUCCESS) {
                    return status;
                }
            } else if (!m_topFieldPictures.empty()) {
                findReusePicture(m_topFieldPictures, reuseSurface);
                if (status != YAMI_SUCCESS) {
                    return status;
                }
            }
        }
        if (!reuseSurface) {
            status = assignSurface();
            if (status != YAMI_SUCCESS) {
                return status;
            }
            m_bottomFieldPictures.push_back(m_currentPicture);
        }
    } else {

        DEBUG("Create a Frame Picture");
        status = assignSurface();
        if (status != YAMI_SUCCESS) {
            return status;
        }
    }
    return status;
}

bool VaapiDecoderMPEG2::updateIQMatrix(const QuantMatrices* refIQMatrix,
                                       bool reset)
{

    // when a new update to the IQMatrix is received, one or more can be
    // updated, leaving those previously updated untouched if this is an
    // update triggered by a Quant Matrix Extension. A Sequence Header
    // Extension must reset the matrices.

    if (reset)
        m_IQMatrices = IQMatricesRefs();

    if (refIQMatrix->load_intra_quantiser_matrix) {
        m_IQMatrices.intra_quantiser_matrix
            = refIQMatrix->intra_quantiser_matrix;
    }

    if (refIQMatrix->load_non_intra_quantiser_matrix) {
        m_IQMatrices.non_intra_quantiser_matrix
            = refIQMatrix->non_intra_quantiser_matrix;
    }

    if (refIQMatrix->load_chroma_intra_quantiser_matrix) {
        m_IQMatrices.chroma_intra_quantiser_matrix
            = refIQMatrix->chroma_intra_quantiser_matrix;
    }

    if (refIQMatrix->load_chroma_non_intra_quantiser_matrix) {
        m_IQMatrices.chroma_non_intra_quantiser_matrix
            = refIQMatrix->chroma_non_intra_quantiser_matrix;
    }

    return refIQMatrix->load_intra_quantiser_matrix
           || refIQMatrix->load_non_intra_quantiser_matrix
           || refIQMatrix->load_chroma_intra_quantiser_matrix
           || refIQMatrix->load_chroma_non_intra_quantiser_matrix;
}

YamiStatus VaapiDecoderMPEG2::loadIQMatrix()
{
    YamiStatus status = YAMI_SUCCESS;
    VAIQMatrixBufferMPEG2* IQMatrix = NULL;

    if (!m_currentPicture->editIqMatrix(IQMatrix)) {
        ERROR("picture->editIqMatrix failed");
        return YAMI_FAIL;
    }

    if (m_IQMatrices.intra_quantiser_matrix) {
        IQMatrix->load_intra_quantiser_matrix = 1;
        memcpy(IQMatrix->intra_quantiser_matrix,
               m_IQMatrices.intra_quantiser_matrix, 64);
    }

    if (m_IQMatrices.non_intra_quantiser_matrix) {
        IQMatrix->load_non_intra_quantiser_matrix = 1;
        memcpy(IQMatrix->non_intra_quantiser_matrix,
               m_IQMatrices.non_intra_quantiser_matrix, 64);
    }

    if (m_IQMatrices.chroma_intra_quantiser_matrix) {
        IQMatrix->load_chroma_intra_quantiser_matrix = 1;
        memcpy(IQMatrix->chroma_intra_quantiser_matrix,
               m_IQMatrices.chroma_intra_quantiser_matrix, 64);
    }

    if (m_IQMatrices.chroma_non_intra_quantiser_matrix) {
        IQMatrix->load_chroma_non_intra_quantiser_matrix = 1;
        memcpy(IQMatrix->chroma_non_intra_quantiser_matrix,
               m_IQMatrices.chroma_non_intra_quantiser_matrix, 64);
    }

    return status;
}

YamiStatus VaapiDecoderMPEG2::createPicture()
{
    YamiStatus status = YAMI_SUCCESS;
    VAPictureParameterBufferMPEG2* mpeg2PictureParams;

    status = assignPicture();
    if (status != YAMI_SUCCESS) {
        return status;
    }

    m_currentPicture->m_temporalReference_
        = m_pictureHeader->temporal_reference;
    m_currentPicture->m_pictureCodingType_
        = m_pictureHeader->picture_coding_type;

    switch (m_pictureCodingExtension->picture_structure) {
    case kTopField:
        m_currentPicture->m_VAPictureStructure_
            = VAAPI_PICTURE_TOP_FIELD;
        break;
    case kBottomField:
        m_currentPicture->m_VAPictureStructure_
            = VAAPI_PICTURE_BOTTOM_FIELD;
        break;
    case kFramePicture:
        m_currentPicture->m_VAPictureStructure_
            = VAAPI_PICTURE_FRAME;
        break;
    default:
        break;
    }

    m_currentPicture->m_topFieldFirst_
        = m_pictureCodingExtension->top_field_first;
    m_currentPicture->m_progressiveFrame_
        = m_pictureCodingExtension->progressive_frame;

    if (!m_currentPicture->editPicture(mpeg2PictureParams)) {
        ERROR("picture->editPicture failed");
        return YAMI_FAIL;
    }

    fillPictureParams(mpeg2PictureParams, m_currentPicture);

    if (m_loadNewIQMatrix) {
        status = loadIQMatrix();
        if (status != YAMI_SUCCESS) {
            ERROR("loadIQMatrix failed");
            return status;
        }
        m_loadNewIQMatrix = false;
    }

    return status;
}

YamiStatus VaapiDecoderMPEG2::decodePicture()
{
    YamiStatus status;

    if (!m_currentPicture->decode()) {
        DEBUG("picture->decode failed");
        return YAMI_FAIL;
    }
    // put full picture into the dpb
    status = m_DPB.insertPicture(m_currentPicture);
    if (status != YAMI_SUCCESS) {
        ERROR("insertPicture to DPB failed");
    }

    return status;
}

YamiStatus VaapiDecoderMPEG2::outputPicture(const PicturePtr& picture)
{
    VaapiDecoderBase::PicturePtr basePicture
        = std::static_pointer_cast<VaapiDecPicture>(picture);

    return VaapiDecoderBase::outputPicture(basePicture);
}

YamiStatus VaapiDecoderMPEG2::decode(VideoDecodeBuffer* buffer)
{
    YamiParser::MPEG2::StartCodeType next_code;
    YamiStatus status = YAMI_SUCCESS;
    YamiParser::MPEG2::ExtensionIdentifierType extID;

    // safe check, client can retry if this happens
    if (!buffer) {
        return YAMI_SUCCESS;
    }

    if (buffer->data == NULL || buffer->size == 0) {
        // no information provided in the buffer
        return YAMI_SUCCESS;
    }

    m_stream->data = buffer->data;
    m_stream->streamSize = buffer->size;
    m_stream->time_stamp = buffer->timeStamp;
    m_currentPTS = buffer->timeStamp;

    DEBUG("decode size %ld timeStamp %" PRIu64 "", m_stream->streamSize,
          m_stream->time_stamp);

    if (m_stream->streamSize < YamiParser::MPEG2::kStartCodeSize) {
        // not enough buffer, client to provide more input
        return YAMI_SUCCESS;
    }

    NalReader nalReader(m_stream->data, m_stream->streamSize);

    while (nalReader.read(m_stream->nalData, m_stream->nalSize)) {
        m_parser->nextStartCode(m_stream.get(), next_code);
        DEBUG("Next start_code %x", next_code);

        switch (next_code) {
        case YamiParser::MPEG2::MPEG2_SEQUENCE_HEADER_CODE:
            // the stream hasn't provided enough information to
            // properly configure it
            DEBUG("decode tries for a sequence_header");
            if (!m_VAStart) {
                status = processConfigBuffer();
                if (status != YAMI_SUCCESS
                    && status != YAMI_DECODE_FORMAT_CHANGE) {
                    return status;
                }
            } else {
                INFO("processDecodeBuffer is called");
                status = processDecodeBuffer();
                if (status != YAMI_SUCCESS) {
                    return status;
                }
            }
            break;
        case YamiParser::MPEG2::MPEG2_GROUP_START_CODE:
        case YamiParser::MPEG2::MPEG2_PICTURE_START_CODE:
        case YamiParser::MPEG2::MPEG2_SEQUENCE_END_CODE:
            status = processDecodeBuffer();
            if (status != YAMI_SUCCESS) {
                return status;
            }
            break;
        case YamiParser::MPEG2::MPEG2_EXTENSION_START_CODE:
            extID = static_cast<YamiParser::MPEG2::ExtensionIdentifierType>(
                m_stream->nalData[1] >> 4);
            if (m_previousStartCode
                == YamiParser::MPEG2::MPEG2_SEQUENCE_HEADER_CODE) {
                status = processConfigBuffer();
                if (status != YAMI_SUCCESS
                    && status != YAMI_DECODE_FORMAT_CHANGE) {
                    return status;
                }
            }
            else if (m_previousStartCode
                         == YamiParser::MPEG2::MPEG2_PICTURE_START_CODE
                     || extID == YamiParser::MPEG2::kQuantizationMatrix
                     || extID == YamiParser::MPEG2::kPictureCoding
                     || extID == YamiParser::MPEG2::kSequence) {
                status = processDecodeBuffer();
                if (status != YAMI_SUCCESS) {
                    return status;
                }
            }
            break;
        default:
            if (isSliceCode(next_code) && m_isParsingSlices) {
                status = processDecodeBuffer();
                if (status != YAMI_SUCCESS) {
                    return status;
                }
            }
            break;
        }
    }

    return YAMI_SUCCESS;
}

void VaapiDecoderMPEG2::fillPictureParams(VAPictureParameterBufferMPEG2* param,
                                          const PicturePtr& currentPicture)
{
    PicturePtr previousPicture, nextPicture;
    param->horizontal_size = m_configBuffer.width;
    param->vertical_size = m_configBuffer.height;
    param->picture_coding_type = m_pictureHeader->picture_coding_type;

    DEBUG("fillPictureParams picture coding type %d",
          param->picture_coding_type);

    m_DPB.getReferencePictures(currentPicture, previousPicture,
                                     nextPicture);

    // I-Frame does not use references
    param->forward_reference_picture = VA_INVALID_ID;
    param->backward_reference_picture = VA_INVALID_ID;
    if (param->picture_coding_type == YamiParser::MPEG2::kPFrame) {
        if (previousPicture) {
            DEBUG("Surface id on previous picture %d",
                  previousPicture->getSurfaceID());
            param->forward_reference_picture = previousPicture->getSurfaceID();
        }
    } else if (param->picture_coding_type == YamiParser::MPEG2::kBFrame) {
        if (previousPicture) {
            DEBUG("Surface id on previous picture %d",
                  previousPicture->getSurfaceID());
            param->forward_reference_picture = previousPicture->getSurfaceID();
        }
        if (nextPicture) {
            DEBUG("Surface id on next picture %d", nextPicture->getSurfaceID());
            param->backward_reference_picture = nextPicture->getSurfaceID();
        }
    }

    // libva packs all in one int.
    param->f_code = ((m_pictureCodingExtension->f_code[0][0] << 12)
                     | (m_pictureCodingExtension->f_code[0][1] << 8)
                     | (m_pictureCodingExtension->f_code[1][0] << 4)
                     | m_pictureCodingExtension->f_code[1][1]);

    param->picture_coding_extension.bits.intra_dc_precision
        = m_pictureCodingExtension->intra_dc_precision;
    param->picture_coding_extension.bits.picture_structure
        = m_pictureCodingExtension->picture_structure;
    param->picture_coding_extension.bits.top_field_first
        = m_pictureCodingExtension->top_field_first;
    param->picture_coding_extension.bits.frame_pred_frame_dct
        = m_pictureCodingExtension->frame_pred_frame_dct;
    param->picture_coding_extension.bits.concealment_motion_vectors
        = m_pictureCodingExtension->concealment_motion_vectors;
    param->picture_coding_extension.bits.q_scale_type
        = m_pictureCodingExtension->q_scale_type;
    param->picture_coding_extension.bits.intra_vlc_format
        = m_pictureCodingExtension->intra_vlc_format;
    param->picture_coding_extension.bits.alternate_scan
        = m_pictureCodingExtension->alternate_scan;
    param->picture_coding_extension.bits.repeat_first_field
        = m_pictureCodingExtension->repeat_first_field;
    param->picture_coding_extension.bits.progressive_frame
        = m_pictureCodingExtension->progressive_frame;

    DEBUG("currentPic temporalRef %d Progressive %d isFirstField %d",
          currentPicture->m_temporalReference_,
          currentPicture->m_progressiveFrame_, currentPicture->m_isFirstField_);
    if (currentPicture->m_progressiveFrame_ == 1
        || currentPicture->m_isFirstField_ == 1) {
        param->picture_coding_extension.bits.is_first_field = 1;
    } else {
        param->picture_coding_extension.bits.is_first_field = 0;
    }
}

void
VaapiDecoderMPEG2::fillSliceParams(VASliceParameterBufferMPEG2* slice_param,
                                   const Slice* slice)
{
    slice_param->macroblock_offset = slice->sliceHeaderSize;
    slice_param->slice_data_flag = VA_SLICE_DATA_FLAG_ALL;
    slice_param->slice_horizontal_position = slice->macroblockColumn;
    slice_param->slice_vertical_position = slice->macroblockRow;
    slice_param->quantiser_scale_code = slice->quantiser_scale_code;
    slice_param->intra_slice_flag = slice->intra_slice_flag;
}

YamiStatus VaapiDecoderMPEG2::reset(VideoConfigBuffer* buffer)
{
    DEBUG("MPEG2: reset()");
    return VaapiDecoderBase::reset(buffer);
}

void VaapiDecoderMPEG2::stop()
{
    DEBUG("MPEG2: stop()");
    flush();
    VaapiDecoderBase::stop();
}

void VaapiDecoderMPEG2::flush()
{
    DEBUG("MPEG2: flush()");
    m_DPB.flush();
    m_parser.reset(new Parser());
    VaapiDecoderBase::flush();
}

}
