/*
 * Copyright 2015 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 <utils/Log.h>

#define DEBUG  0
#if DEBUG
#  define  DDD(...)    ALOGD(__VA_ARGS__)
#else
#  define  DDD(...)    ((void)0)
#endif

#include "GoldfishAVCDec.h"

#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/MediaDefs.h>
#include <OMX_VideoExt.h>
#include <inttypes.h>

#include <nativebase/nativebase.h>

#include <android/hardware/graphics/common/1.2/types.h>
#include <hidl/LegacySupport.h>

using ::android::hardware::graphics::common::V1_2::PixelFormat;
using ::android::hardware::graphics::common::V1_0::BufferUsage;

namespace android {

#define componentName                   "video_decoder.avc"
#define codingType                      OMX_VIDEO_CodingAVC
#define CODEC_MIME_TYPE                 MEDIA_MIMETYPE_VIDEO_AVC

/** Function and structure definitions to keep code similar for each codec */
#define ivdec_api_function              ih264d_api_function
#define ivdext_create_ip_t              ih264d_create_ip_t
#define ivdext_create_op_t              ih264d_create_op_t
#define ivdext_delete_ip_t              ih264d_delete_ip_t
#define ivdext_delete_op_t              ih264d_delete_op_t
#define ivdext_ctl_set_num_cores_ip_t   ih264d_ctl_set_num_cores_ip_t
#define ivdext_ctl_set_num_cores_op_t   ih264d_ctl_set_num_cores_op_t

#define IVDEXT_CMD_CTL_SET_NUM_CORES    \
        (IVD_CONTROL_API_COMMAND_TYPE_T)IH264D_CMD_CTL_SET_NUM_CORES

static const CodecProfileLevel kProfileLevels[] = {
    { OMX_VIDEO_AVCProfileConstrainedBaseline, OMX_VIDEO_AVCLevel52 },

    { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel52 },

    { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel52 },

    { OMX_VIDEO_AVCProfileConstrainedHigh,     OMX_VIDEO_AVCLevel52 },

    { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel52 },
};

GoldfishAVCDec::GoldfishAVCDec(
        const char *name,
        const OMX_CALLBACKTYPE *callbacks,
        OMX_PTR appData,
        OMX_COMPONENTTYPE **component, RenderMode renderMode)
    : GoldfishVideoDecoderOMXComponent(
            name, componentName, codingType,
            kProfileLevels, ARRAY_SIZE(kProfileLevels),
            320 /* width */, 240 /* height */, callbacks,
            appData, component),
      mOmxColorFormat(OMX_COLOR_FormatYUV420Planar),
      mChangingResolution(false),
      mSignalledError(false),
      mInputOffset(0), mRenderMode(renderMode){
    initPorts(
            1 /* numMinInputBuffers */, kNumBuffers, INPUT_BUF_SIZE,
            1 /* numMinOutputBuffers */, kNumBuffers, CODEC_MIME_TYPE);

    mTimeStart = mTimeEnd = systemTime();

    // If input dump is enabled, then open create an empty file
    GENERATE_FILE_NAMES();
    CREATE_DUMP_FILE(mInFile);
    ALOGI("created %s %d object %p", __func__, __LINE__, this);
}

GoldfishAVCDec::~GoldfishAVCDec() {
    CHECK_EQ(deInitDecoder(), (status_t)OK);
    DDD("destroyed %s %d object %p", __func__, __LINE__, this);
}

void GoldfishAVCDec::logVersion() {
    // TODO: get emulation decoder implementation version from the host.
    ALOGI("GoldfishAVC decoder version 1.0");
}

status_t GoldfishAVCDec::resetPlugin() {
    mIsInFlush = false;
    mReceivedEOS = false;

    /* Initialize both start and end times */
    mTimeStart = mTimeEnd = systemTime();

    return OK;
}

status_t GoldfishAVCDec::resetDecoder() {
    if (mContext) {
    // The resolution may have changed, so our safest bet is to just destroy the
    // current context and recreate another one, with the new width and height.
    mContext->destroyH264Context();
    mContext.reset(nullptr);

    }
    return OK;
}

status_t GoldfishAVCDec::setFlushMode() {
    /* Set the decoder in Flush mode, subsequent decode() calls will flush */
    mIsInFlush = true;
    mContext->flush();
    return OK;
}

status_t GoldfishAVCDec::initDecoder() {
    /* Initialize the decoder */
    if (mEnableAndroidNativeBuffers == false) {
        mRenderMode = RenderMode::RENDER_BY_GUEST_CPU;
    }
    mContext.reset(new MediaH264Decoder(mRenderMode));
    mContext->initH264Context(mWidth,
                              mHeight,
                              mWidth,
                              mHeight,
                              MediaH264Decoder::PixelFormat::YUV420P);

    /* Reset the plugin state */
    resetPlugin();

    /* Get codec version */
    logVersion();

    return OK;
}

status_t GoldfishAVCDec::deInitDecoder() {
    if (mContext) {
        mContext->destroyH264Context();
        mContext.reset();
    }

    mChangingResolution = false;

    return OK;
}

void GoldfishAVCDec::onReset() {
    GoldfishVideoDecoderOMXComponent::onReset();

    mSignalledError = false;
    mInputOffset = 0;
    resetDecoder();
    resetPlugin();
}

bool GoldfishAVCDec::getVUIParams(h264_image_t& img) {
    int32_t primaries = img.color_primaries;
    bool fullRange = img.color_range == 2 ? true : false;
    int32_t transfer = img.color_trc;
    int32_t coeffs = img.colorspace;

    ColorAspects colorAspects;
    ColorUtils::convertIsoColorAspectsToCodecAspects(
            primaries, transfer, coeffs, fullRange, colorAspects);

    DDD("img pts %lld, primaries %d, range %d transfer %d colorspace %d", (long long)img.pts,
            (int)img.color_primaries, (int)img.color_range, (int)img.color_trc, (int)img.colorspace);

    // Update color aspects if necessary.
    if (colorAspectsDiffer(colorAspects, mBitstreamColorAspects)) {
        mBitstreamColorAspects = colorAspects;
        status_t err = handleColorAspectsChange();
        CHECK(err == OK);
    }
    return true;
}

bool GoldfishAVCDec::setDecodeArgs(
        OMX_BUFFERHEADERTYPE *inHeader,
        OMX_BUFFERHEADERTYPE *outHeader) {
    size_t sizeY = outputBufferWidth() * outputBufferHeight();
    size_t sizeUV = sizeY / 4;

    /* When in flush and after EOS with zero byte input,
     * inHeader is set to zero. Hence check for non-null */
    if (inHeader) {
        mConsumedBytes = inHeader->nFilledLen - mInputOffset;
        mInPBuffer = inHeader->pBuffer + inHeader->nOffset + mInputOffset;
        DDD("got input timestamp %lld in-addr-base %p real-data-offset %d inputoffset %d", (long long)(inHeader->nTimeStamp),
                inHeader->pBuffer, (int)(inHeader->nOffset + mInputOffset), (int)mInputOffset);
    } else {
        mConsumedBytes = 0;
        mInPBuffer = nullptr;
    }

    if (outHeader) {
        if (outHeader->nAllocLen < sizeY + (sizeUV * 2)) {
            ALOGE("outHeader->nAllocLen %d < needed size %d", outHeader->nAllocLen, (int)(sizeY + sizeUV * 2));
            android_errorWriteLog(0x534e4554, "27833616");
            return false;
        }
        mOutHeaderBuf = outHeader->pBuffer;
    } else {
        // We flush out on the host side
        mOutHeaderBuf = nullptr;
    }

    return true;
}

void GoldfishAVCDec::readAndDiscardAllHostBuffers() {
    while (mContext) {
        h264_image_t img = mContext->getImage();
        if (img.data != nullptr) {
            DDD("img pts %lld is discarded", (long long)img.pts);
        } else {
            return;
        }
    }
}

void GoldfishAVCDec::onPortFlushCompleted(OMX_U32 portIndex) {
    /* Once the output buffers are flushed, ignore any buffers that are held in decoder */
    if (kOutputPortIndex == portIndex) {
        setFlushMode();
        DDD("%s %d", __func__, __LINE__);
        readAndDiscardAllHostBuffers();
        mContext->resetH264Context(mWidth, mHeight, mWidth, mHeight, MediaH264Decoder::PixelFormat::YUV420P);
        if (!mCsd0.empty() && !mCsd1.empty()) {
            mContext->decodeFrame(&(mCsd0[0]), mCsd0.size(), 0);
            mContext->getImage();
            mContext->decodeFrame(&(mCsd1[0]), mCsd1.size(), 0);
            mContext->getImage();
        }
        resetPlugin();
    } else {
        mInputOffset = 0;
    }
}

void GoldfishAVCDec::copyImageData( OMX_BUFFERHEADERTYPE *outHeader, h264_image_t & img) {
    int myStride = outputBufferWidth();
    for (int i=0; i < mHeight; ++i) {
        memcpy(outHeader->pBuffer + i * myStride, img.data + i * mWidth, mWidth);
    }
    int Y = myStride * outputBufferHeight();
    for (int i=0; i < mHeight/2; ++i) {
        memcpy(outHeader->pBuffer + Y + i * myStride / 2 , img.data + mWidth * mHeight + i * mWidth/2, mWidth/2);
    }
    int UV = Y/4;
    for (int i=0; i < mHeight/2; ++i) {
        memcpy(outHeader->pBuffer + Y + UV + i * myStride / 2 , img.data + mWidth * mHeight * 5/4 + i * mWidth/2, mWidth/2);
    }
}

int GoldfishAVCDec::getHostColorBufferId(void* header) {
  if (mNWBuffers.find(header) == mNWBuffers.end()) {
      DDD("cannot find color buffer for header %p", header);
    return -1;
  }
  sp<ANativeWindowBuffer> nBuf = mNWBuffers[header];
  cb_handle_t *handle = (cb_handle_t*)nBuf->handle;
  DDD("found color buffer for header %p --> %d", header, handle->hostHandle);
  return handle->hostHandle;
}

void GoldfishAVCDec::onQueueFilled(OMX_U32 portIndex) {
    static int count1=0;
    DDD("calling %s count %d object %p", __func__, ++count1, this);
    UNUSED(portIndex);
    OMX_BUFFERHEADERTYPE *inHeader = NULL;
    BufferInfo *inInfo = NULL;

    if (mSignalledError) {
        return;
    }
    if (mOutputPortSettingsChange != NONE) {
        return;
    }

    if (mContext == nullptr) {
        if (OK != initDecoder()) {
            ALOGE("Failed to initialize decoder");
            notify(OMX_EventError, OMX_ErrorUnsupportedSetting, 0, NULL);
            mSignalledError = true;
            return;
        }
    }

    List<BufferInfo *> &inQueue = getPortQueue(kInputPortIndex);
    List<BufferInfo *> &outQueue = getPortQueue(kOutputPortIndex);

    int count2=0;
    while (!outQueue.empty()) {
        DDD("calling %s in while loop count %d", __func__, ++count2);
        BufferInfo *outInfo;
        OMX_BUFFERHEADERTYPE *outHeader;

        if (!mIsInFlush && (NULL == inHeader)) {
            if (!inQueue.empty()) {
                inInfo = *inQueue.begin();
                inHeader = inInfo->mHeader;
                if (inHeader == NULL) {
                    inQueue.erase(inQueue.begin());
                    inInfo->mOwnedByUs = false;
                    continue;
                }
            } else {
                break;
            }
        }

        outInfo = *outQueue.begin();
        outHeader = outInfo->mHeader;
        outHeader->nFlags = 0;
        outHeader->nTimeStamp = 0;
        outHeader->nOffset = 0;

        if (inHeader != NULL) {
            if (inHeader->nFilledLen == 0) {
                // An empty buffer can be end of stream (EOS) buffer, so
                // we'll set the decoder in flush mode if so. If it's not EOS,
                // then just release the buffer.
                inQueue.erase(inQueue.begin());
                inInfo->mOwnedByUs = false;
                notifyEmptyBufferDone(inHeader);

                if (!(inHeader->nFlags & OMX_BUFFERFLAG_EOS)) {
                    return;
                }

                mReceivedEOS = true;
                inHeader = NULL;
                setFlushMode();
            } else if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) {
                mReceivedEOS = true;
            }
        }

        {

            if (!setDecodeArgs(inHeader, outHeader)) {
                ALOGE("Decoder arg setup failed");
                notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
                mSignalledError = true;
                return;
            }

            // TODO: We also need to send the timestamp
            h264_result_t h264Res = {(int)MediaH264Decoder::Err::NoErr, 0};
            if (inHeader != nullptr) {
                if (inHeader->nFlags & OMX_BUFFERFLAG_CODECCONFIG) { 
                    unsigned long mysize = (inHeader->nFilledLen - mInputOffset);
                    uint8_t* mydata = mInPBuffer;
                    if (mCsd0.empty()) {
                        mCsd0.assign(mydata, mydata + mysize);
                    } else if (mCsd1.empty()) {
                        mCsd1.assign(mydata, mydata + mysize);
                    }
                }
                DDD("Decoding frame(sz=%lu)", (unsigned long)(inHeader->nFilledLen - mInputOffset));
                h264Res = mContext->decodeFrame(mInPBuffer,
                                                inHeader->nFilledLen - mInputOffset,
                                                inHeader->nTimeStamp);
                mConsumedBytes = h264Res.bytesProcessed;
                if (h264Res.ret == (int)MediaH264Decoder::Err::DecoderRestarted) {
                    mChangingResolution = true;
                }
            } else {
                DDD("No more input data. Attempting to get a decoded frame, if any.");
            }
            h264_image_t img = {};

            bool readBackPixels = true;
            if (mRenderMode == RenderMode::RENDER_BY_GUEST_CPU) {
              img = mContext->getImage();
            } else {
                int hostColorBufferId = getHostColorBufferId(outHeader);
                if (hostColorBufferId >= 0) {
                    img = mContext->renderOnHostAndReturnImageMetadata(getHostColorBufferId(outHeader));
                    readBackPixels = false;
                } else {
                    img = mContext->getImage();
                }
            }


            if (img.data != nullptr) {
                getVUIParams(img);
            }


            if (inHeader) {
                DDD("input time stamp %lld flag %d", inHeader->nTimeStamp, (int)(inHeader->nFlags));
            }

            // If the decoder is in the changing resolution mode and there is no output present,
            // that means the switching is done and it's ready to reset the decoder and the plugin.
            if (mChangingResolution && img.data == nullptr) {
                mChangingResolution = false;
                DDD("re-create decoder because resolution changed");
                bool portWillReset = false;
                handlePortSettingsChange(&portWillReset, img.width, img.height);
                {
                    DDD("handling port reset");
                    DDD("port resetting (img.width=%u, img.height=%u, mWidth=%u, mHeight=%u)",
                          img.width, img.height, mWidth, mHeight);
                    //resetDecoder();
                    resetPlugin();

                //mContext->destroyH264Context();
                //mContext.reset(new MediaH264Decoder());
                mContext->resetH264Context(mWidth,
                              mHeight,
                              mWidth,
                              mHeight,
                              MediaH264Decoder::PixelFormat::YUV420P);
                //mInputOffset += mConsumedBytes;
                return;
                }
            }

            if (img.data != nullptr) {
                int myWidth = img.width;
                int myHeight = img.height;
                if (myWidth != mWidth || myHeight != mHeight) {
                    bool portWillReset = false;
                    handlePortSettingsChange(&portWillReset, myWidth, myHeight);
                    resetPlugin();
                    mWidth = myWidth;
                    mHeight = myHeight;
                    if (portWillReset) {
                        DDD("port will reset return now");
                        return;
                    } else {
                        DDD("port will NOT reset keep going now");
                    }
                }
                outHeader->nFilledLen =  (outputBufferWidth() * outputBufferHeight() * 3) / 2;
                if (readBackPixels) {
                  if (outputBufferWidth() == mWidth && outputBufferHeight() == mHeight) {
                    memcpy(outHeader->pBuffer, img.data, outHeader->nFilledLen);
                  } else {
                    copyImageData(outHeader, img);
                  }
                }

                outHeader->nTimeStamp = img.pts;
                DDD("got output timestamp %lld", (long long)(img.pts));

                outInfo->mOwnedByUs = false;
                outQueue.erase(outQueue.begin());
                outInfo = NULL;
                notifyFillBufferDone(outHeader);
                outHeader = NULL;
            } else if (mIsInFlush) {
                DDD("not img.data and it is in flush mode");
                /* If in flush mode and no output is returned by the codec,
                 * then come out of flush mode */
                mIsInFlush = false;

                /* If EOS was recieved on input port and there is no output
                 * from the codec, then signal EOS on output port */
                if (mReceivedEOS) {
                    ALOGI("received EOS, re-create host context");
                    outHeader->nFilledLen = 0;
                    outHeader->nFlags |= OMX_BUFFERFLAG_EOS;

                    outInfo->mOwnedByUs = false;
                    outQueue.erase(outQueue.begin());
                    outInfo = NULL;
                    notifyFillBufferDone(outHeader);
                    outHeader = NULL;
                    resetPlugin();

                    //mContext->destroyH264Context();
                //mContext.reset(new MediaH264Decoder());
                    mContext->resetH264Context(mWidth,
                              mHeight,
                              mWidth,
                              mHeight,
                              MediaH264Decoder::PixelFormat::YUV420P);

                }
            }
            mInputOffset += mConsumedBytes;
        }

        // If more than 4 bytes are remaining in input, then do not release it
        if (inHeader != NULL && ((inHeader->nFilledLen - mInputOffset) <= 4)) {
            inInfo->mOwnedByUs = false;
            inQueue.erase(inQueue.begin());
            inInfo = NULL;
            notifyEmptyBufferDone(inHeader);
            inHeader = NULL;
            mInputOffset = 0;

            /* If input EOS is seen and decoder is not in flush mode,
             * set the decoder in flush mode.
             * There can be a case where EOS is sent along with last picture data
             * In that case, only after decoding that input data, decoder has to be
             * put in flush. This case is handled here  */

            if (mReceivedEOS && !mIsInFlush) {
                setFlushMode();
            }
        }
    }
}

OMX_ERRORTYPE GoldfishAVCDec::internalGetParameter(
        OMX_INDEXTYPE index, OMX_PTR params) {
    const int32_t indexFull = index;
    switch (indexFull) {
        case kGetAndroidNativeBufferUsageIndex:
        {
            DDD("calling kGetAndroidNativeBufferUsageIndex");
            GetAndroidNativeBufferUsageParams* nativeBuffersUsage = (GetAndroidNativeBufferUsageParams *) params;
            nativeBuffersUsage->nUsage = (unsigned int)(BufferUsage::GPU_DATA_BUFFER);
            return OMX_ErrorNone;
        }

        default:
            return GoldfishVideoDecoderOMXComponent::internalGetParameter(index, params);
    }
}

OMX_ERRORTYPE GoldfishAVCDec::internalSetParameter(
        OMX_INDEXTYPE index, const OMX_PTR params) {
    // Include extension index OMX_INDEXEXTTYPE.
    const int32_t indexFull = index;

    switch (indexFull) {
        case kEnableAndroidNativeBuffersIndex:
        {
            DDD("calling kEnableAndroidNativeBuffersIndex");
            EnableAndroidNativeBuffersParams* enableNativeBuffers = (EnableAndroidNativeBuffersParams *) params;
            if (enableNativeBuffers) {
                mEnableAndroidNativeBuffers = enableNativeBuffers->enable;
                if (mEnableAndroidNativeBuffers == false) {
                    mNWBuffers.clear();
                    DDD("disabled kEnableAndroidNativeBuffersIndex");
                } else {
                    DDD("enabled kEnableAndroidNativeBuffersIndex");
                }
            }
            return OMX_ErrorNone;
        }

        case kUseAndroidNativeBufferIndex:
        {
            if (mEnableAndroidNativeBuffers == false) {
                ALOGE("Error: not enabled Android Native Buffers");
                return OMX_ErrorBadParameter;
            }
            UseAndroidNativeBufferParams *use_buffer_params = (UseAndroidNativeBufferParams *)params;
            if (use_buffer_params) {
                sp<ANativeWindowBuffer> nBuf = use_buffer_params->nativeBuffer;
                cb_handle_t *handle = (cb_handle_t*)nBuf->handle;
                void* dst = NULL;
                DDD("kUseAndroidNativeBufferIndex with handle %p host color handle %d calling usebuffer", handle,
                      handle->hostHandle);
                useBufferCallerLockedAlready(use_buffer_params->bufferHeader,use_buffer_params->nPortIndex,
                        use_buffer_params->pAppPrivate,handle->allocatedSize(), (OMX_U8*)dst);
                mNWBuffers[*(use_buffer_params->bufferHeader)] = use_buffer_params->nativeBuffer;;
            }
            return OMX_ErrorNone;
        }

        default:
            return GoldfishVideoDecoderOMXComponent::internalSetParameter(index, params);
    }
}

OMX_ERRORTYPE GoldfishAVCDec::getExtensionIndex(
        const char *name, OMX_INDEXTYPE *index) {

    if (mRenderMode == RenderMode::RENDER_BY_HOST_GPU) {
        if (!strcmp(name, "OMX.google.android.index.enableAndroidNativeBuffers")) {
            DDD("calling getExtensionIndex for enable ANB");
            *(int32_t*)index = kEnableAndroidNativeBuffersIndex;
            return OMX_ErrorNone;
        } else if (!strcmp(name, "OMX.google.android.index.useAndroidNativeBuffer")) {
            *(int32_t*)index = kUseAndroidNativeBufferIndex;
            return OMX_ErrorNone;
        } else if (!strcmp(name, "OMX.google.android.index.getAndroidNativeBufferUsage")) {
            *(int32_t*)index = kGetAndroidNativeBufferUsageIndex;
            return OMX_ErrorNone;
        }
    }
    return GoldfishVideoDecoderOMXComponent::getExtensionIndex(name, index);
}

int GoldfishAVCDec::getColorAspectPreference() {
    return kPreferBitstream;
}

}  // namespace android

android::GoldfishOMXComponent *createGoldfishOMXComponent(
        const char *name, const OMX_CALLBACKTYPE *callbacks, OMX_PTR appData,
        OMX_COMPONENTTYPE **component) {
    if (!strncmp("OMX.android.goldfish", name, 20)) {
      return new android::GoldfishAVCDec(name, callbacks, appData, component, RenderMode::RENDER_BY_HOST_GPU);
    } else {
      return new android::GoldfishAVCDec(name, callbacks, appData, component, RenderMode::RENDER_BY_GUEST_CPU);
    }
}

