/*
* Copyright (c) 2009-2011 Intel Corporation.  All rights reserved.
*
* 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 "IntelMetadataBuffer"
#include <wrs_omxil_core/log.h>

#include "IntelMetadataBuffer.h"
#include <string.h>
#include <stdio.h>

#ifdef INTEL_VIDEO_XPROC_SHARING
#include <binder/IServiceManager.h>
#include <binder/MemoryBase.h>
#include <binder/Parcel.h>
#include <utils/List.h>
#include <utils/threads.h>
#include <ui/GraphicBuffer.h>

//#define TEST

struct ShareMemMap {
    uint32_t sessionflag;
    intptr_t value;
    intptr_t value_backup;
    uint32_t type;
    sp<MemoryBase> membase;
    sp<GraphicBuffer> gbuffer;
};

List <ShareMemMap *> gShareMemMapList;
Mutex gShareMemMapListLock;

enum {
    SHARE_MEM = IBinder::FIRST_CALL_TRANSACTION,
    GET_MEM,
    CLEAR_MEM,
};

enum {
    ST_MEMBASE = 0,
    ST_GFX,
    ST_MAX,
};

#define REMOTE_PROVIDER 0x80000000
#define REMOTE_CONSUMER 0x40000000

static ShareMemMap* ReadMemObjFromBinder(const Parcel& data, uint32_t sessionflag, intptr_t value) {

    uint32_t type = data.readInt32();
    if (type >= ST_MAX)
        return NULL;

    ShareMemMap* map = new ShareMemMap;
    map->sessionflag = sessionflag;
    map->type = type;
    map->value_backup = value;
    map->membase = NULL;
    map->gbuffer= NULL;

//    LOGI("ReadMemObjFromBinder");

    if (type == ST_MEMBASE) /*offset, size, heap*/
    {
        ssize_t offset = data.readInt32();
        size_t size = data.readInt32();

        sp<IMemoryHeap> heap = interface_cast<IMemoryHeap>(data.readStrongBinder());

        sp<MemoryBase> mem = new MemoryBase(heap, offset, size);
        if (mem == NULL)
        {
            delete map;
            return NULL;
        }

        map->value = (intptr_t)( mem->pointer() + 0x0FFF) & ~0x0FFF;
        map->membase = mem;

#ifdef TEST
        ALOGI("membase heapID:%d, pointer:%x data:%x, aligned value:%x", \
           heap->getHeapID(), mem->pointer(), *((intptr_t *)(mem->pointer())), map->value);
#endif

    }
    else if (type == ST_GFX) /*graphicbuffer*/
    {
        sp<GraphicBuffer> buffer = new GraphicBuffer();
        if (buffer == NULL)
        {
            delete map;
            return NULL;
        }
        data.read(*buffer);

        map->value = (intptr_t)buffer->handle;
        map->gbuffer = buffer;

#ifdef TEST
        void* usrptr[3];
        buffer->lock(GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_READ_OFTEN, &usrptr[0]);
        buffer->unlock();
        ALOGI("gfx handle:%p data:%x", (intptr_t)buffer->handle, *((intptr_t *)usrptr[0]));
#endif
    }

    gShareMemMapListLock.lock();
    gShareMemMapList.push_back(map);
    gShareMemMapListLock.unlock();
    return map;
}

static status_t WriteMemObjToBinder(Parcel& data, ShareMemMap* smem) {

    if (smem->type >= ST_MAX)
        return BAD_VALUE;

//    LOGI("WriteMemObjToBinder");

    data.writeInt32(smem->type);

    if (smem->type == ST_MEMBASE) /*offset, size, heap*/
    {
        ssize_t offset;
        size_t size;
        sp<IMemoryHeap> heap = smem->membase->getMemory(&offset, &size);
        data.writeInt32(offset);
        data.writeInt32(size);
        data.writeStrongBinder(IInterface::asBinder(heap));
#ifdef TEST
        ALOGI("membase heapID:%d pointer:%x data:%x", \
            heap->getHeapID(), smem->membase->pointer(), *((int *)(smem->membase->pointer())));
#endif
    }
    else if (smem->type == ST_GFX) /*graphicbuffer*/
        data.write(*(smem->gbuffer));

    return NO_ERROR;
}

static void ClearLocalMem(uint32_t sessionflag)
{
    List<ShareMemMap *>::iterator node;

    gShareMemMapListLock.lock();

    for(node = gShareMemMapList.begin(); node != gShareMemMapList.end(); )
    {
        if ((*node)->sessionflag == sessionflag) //remove all buffers belong to this session
        {
            (*node)->membase = NULL;
            (*node)->gbuffer = NULL;
            delete (*node);
            node = gShareMemMapList.erase(node);
        }
        else
            node ++;
    }

    gShareMemMapListLock.unlock();
}

static ShareMemMap* FindShareMem(uint32_t sessionflag, intptr_t value, bool isBackup)
{
    List<ShareMemMap *>::iterator node;

    gShareMemMapListLock.lock();
    for(node = gShareMemMapList.begin(); node !=  gShareMemMapList.end(); node++)
    {
        if (isBackup)
        {
            if ((*node)->sessionflag == sessionflag && (*node)->value_backup == value)
            {
                gShareMemMapListLock.unlock();
                return (*node);
            }
        }
        else if ((*node)->sessionflag == sessionflag && (*node)->value == value)
        {
            gShareMemMapListLock.unlock();
            return (*node);
        }
    }
    gShareMemMapListLock.unlock();

    return NULL;
}

static ShareMemMap* PopShareMem(uint32_t sessionflag, intptr_t value)
{
    List<ShareMemMap *>::iterator node;

    gShareMemMapListLock.lock();
    for(node = gShareMemMapList.begin(); node != gShareMemMapList.end(); node++)
    {
        if ((*node)->sessionflag == sessionflag && (*node)->value == value)
        {
            gShareMemMapList.erase(node);
            gShareMemMapListLock.unlock();
            return (*node);
        }
    }
    gShareMemMapListLock.unlock();

    return NULL;
}

static void PushShareMem(ShareMemMap* &smem)
{
    gShareMemMapListLock.lock();
    gShareMemMapList.push_back(smem);
    gShareMemMapListLock.unlock();
}

static sp<IBinder> GetIntelBufferSharingService() {

    sp<IServiceManager> sm = defaultServiceManager();
    sp<IBinder> binder = sm->checkService(String16("media.IntelBufferSharing"));

    if (binder == 0)
        ALOGE("media.IntelBufferSharing service is not published");

    return binder;
}

IntelBufferSharingService* IntelBufferSharingService::gBufferService = NULL;

status_t IntelBufferSharingService::instantiate(){
    status_t ret = NO_ERROR;

    if (gBufferService == NULL) {
        gBufferService = new IntelBufferSharingService();
        ret = defaultServiceManager()->addService(String16("media.IntelBufferSharing"), gBufferService);
        LOGI("IntelBufferSharingService::instantiate() ret = %d\n", ret);
    }

    return ret;
}

status_t IntelBufferSharingService::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {

    //TODO: if pid is int32?
    pid_t pid = data.readInt32();
    uint32_t sessionflag = data.readInt32();

    switch(code)
    {
        case SHARE_MEM:
        {

            if (pid == getpid()) //in same process, should not use binder
            {
                ALOGE("onTransact in same process, wrong sessionflag?");
                return UNKNOWN_ERROR;
            }

            intptr_t value = data.readIntPtr();

//            LOGI("onTransact SHARE_MEM value=%x", value);

            //different process
            ShareMemMap* map = ReadMemObjFromBinder(data, sessionflag, value);
            if (map == NULL)
                return UNKNOWN_ERROR;

            reply->writeIntPtr(map->value);

            return NO_ERROR;
        }
        case CLEAR_MEM:
        {
//            LOGI("onTransact CLEAR_MEM sessionflag=%x", sessionflag);

            if (pid == getpid()) //in same process, should not use binder
            {
                //same process, return same pointer in data
                ALOGE("onTransact CLEAR_MEM in same process, wrong sessionflag?");
                return UNKNOWN_ERROR;
            }

            ClearLocalMem(sessionflag);
            return NO_ERROR;
        }
        case GET_MEM:
        {

            if (pid == getpid()) //in same process, should not use binder
            {
                ALOGE("onTransact GET_MEM in same process, wrong sessionflag?");
                return UNKNOWN_ERROR;
            }

            intptr_t value = data.readIntPtr();

//            LOGI("onTransact GET_MEM value=%x", value);

            ShareMemMap* smem = FindShareMem(sessionflag, value, false);
            if (smem && (NO_ERROR == WriteMemObjToBinder(*reply, smem)))
                return NO_ERROR;
            else
                ALOGE("onTransact GET_MEM: Not find mem");

            return UNKNOWN_ERROR;
        }
        default:
            return BBinder::onTransact(code, data, reply, flags);

    }
    return NO_ERROR;
}
#endif

IntelMetadataBuffer::IntelMetadataBuffer()
{
    mType = IntelMetadataBufferTypeCameraSource;
    mValue = 0;
    mInfo = NULL;
    mExtraValues = NULL;
    mExtraValues_Count = 0;
    mBytes = NULL;
    mSize = 0;
#ifdef INTEL_VIDEO_XPROC_SHARING
    mSessionFlag = 0;
#endif
}

IntelMetadataBuffer::IntelMetadataBuffer(IntelMetadataBufferType type, intptr_t value)
{
    mType = type;
    mValue = value;
    mInfo = NULL;
    mExtraValues = NULL;
    mExtraValues_Count = 0;
    mBytes = NULL;
    mSize = 0;
#ifdef INTEL_VIDEO_XPROC_SHARING
    mSessionFlag = 0;
#endif
}

IntelMetadataBuffer::~IntelMetadataBuffer()
{
    if (mInfo)
        delete mInfo;

    if (mExtraValues)
        delete[] mExtraValues;

    if (mBytes)
        delete[] mBytes;
}


IntelMetadataBuffer::IntelMetadataBuffer(const IntelMetadataBuffer& imb)
     :mType(imb.mType), mValue(imb.mValue), mInfo(NULL), mExtraValues(NULL),
      mExtraValues_Count(imb.mExtraValues_Count), mBytes(NULL), mSize(imb.mSize)
#ifdef INTEL_VIDEO_XPROC_SHARING
      ,mSessionFlag(imb.mSessionFlag)
#endif
{
    if (imb.mInfo)
        mInfo = new ValueInfo(*imb.mInfo);

    if (imb.mExtraValues)
    {
        mExtraValues = new intptr_t[mExtraValues_Count];
        memcpy(mExtraValues, imb.mExtraValues, sizeof(mValue) * mExtraValues_Count);
    }

    if (imb.mBytes)
    {
        mBytes = new uint8_t[mSize];
        memcpy(mBytes, imb.mBytes, mSize);
    }
}

const IntelMetadataBuffer& IntelMetadataBuffer::operator=(const IntelMetadataBuffer& imb)
{
    mType = imb.mType;
    mValue = imb.mValue;
    mInfo = NULL;
    mExtraValues = NULL;
    mExtraValues_Count = imb.mExtraValues_Count;
    mBytes = NULL;
    mSize = imb.mSize;
#ifdef INTEL_VIDEO_XPROC_SHARING
    mSessionFlag = imb.mSessionFlag;
#endif

    if (imb.mInfo)
        mInfo = new ValueInfo(*imb.mInfo);

    if (imb.mExtraValues)
    {
        mExtraValues = new intptr_t[mExtraValues_Count];
        memcpy(mExtraValues, imb.mExtraValues, sizeof(mValue) * mExtraValues_Count);
    }

    if (imb.mBytes)
    {
        mBytes = new uint8_t[mSize];
        memcpy(mBytes, imb.mBytes, mSize);
    }

    return *this;
}

IMB_Result IntelMetadataBuffer::GetType(IntelMetadataBufferType& type)
{
    type = mType;

    return IMB_SUCCESS;
}

IMB_Result IntelMetadataBuffer::SetType(IntelMetadataBufferType type)
{
    if (type < IntelMetadataBufferTypeLast)
        mType = type;
    else
        return IMB_INVAL_PARAM;

    return IMB_SUCCESS;
}

IMB_Result IntelMetadataBuffer::GetValue(intptr_t& value)
{
    value = mValue;

#ifndef INTEL_VIDEO_XPROC_SHARING
    return IMB_SUCCESS;
#else
    if ((mSessionFlag & REMOTE_CONSUMER) == 0) //no sharing or is local consumer
        return IMB_SUCCESS;

    //try to find if it is already cached.
    ShareMemMap* smem = FindShareMem(mSessionFlag, mValue, true);
    if(smem)
    {
        value = smem->value;
        return IMB_SUCCESS;
    }

    //is remote provider and not find from cache, then pull from service
    sp<IBinder> binder = GetIntelBufferSharingService();
    if (binder == 0)
        return IMB_NO_SERVICE;

    //Detect IntelBufferSharingService, share mem to service
    Parcel data, reply;

    //send pid, sessionflag, and memtype
    pid_t pid = getpid();
    //TODO: if pid is int32?
    data.writeInt32(pid);
    data.writeInt32(mSessionFlag);
    data.writeIntPtr(mValue);

    //do transcation
    if (binder->transact(GET_MEM, data, &reply) != NO_ERROR)
        return IMB_SERVICE_FAIL;

    //get type/Mem OBJ
    smem = ReadMemObjFromBinder(reply, mSessionFlag, mValue);
    if (smem)
        value = smem->value;
    else
        return IMB_SERVICE_FAIL;

    return IMB_SUCCESS;
#endif
}

IMB_Result IntelMetadataBuffer::SetValue(intptr_t value)
{
    mValue = value;

    return IMB_SUCCESS;
}

IMB_Result IntelMetadataBuffer::GetValueInfo(ValueInfo* &info)
{
    info = mInfo;

    return IMB_SUCCESS;
}

IMB_Result IntelMetadataBuffer::SetValueInfo(ValueInfo* info)
{
    if (info)
    {
        if (mInfo == NULL)
            mInfo = new ValueInfo;

        memcpy(mInfo, info, sizeof(ValueInfo));
    }
    else
        return IMB_INVAL_PARAM;

    return IMB_SUCCESS;
}

IMB_Result IntelMetadataBuffer::GetExtraValues(intptr_t* &values, uint32_t& num)
{
    values = mExtraValues;
    num = mExtraValues_Count;

    return IMB_SUCCESS;
}

IMB_Result IntelMetadataBuffer::SetExtraValues(intptr_t* values, uint32_t num)
{
    if (values && num > 0)
    {
        if (mExtraValues && mExtraValues_Count != num)
        {
            delete[] mExtraValues;
            mExtraValues = NULL;
        }

        if (mExtraValues == NULL)
            mExtraValues = new intptr_t[num];

        memcpy(mExtraValues, values, sizeof(intptr_t) * num);
        mExtraValues_Count = num;
    }
    else
        return IMB_INVAL_PARAM;

    return IMB_SUCCESS;
}

IMB_Result IntelMetadataBuffer::UnSerialize(uint8_t* data, uint32_t size)
{
    if (!data || size == 0)
        return IMB_INVAL_PARAM;

    IntelMetadataBufferType type;
    intptr_t value;
    uint32_t extrasize = size - sizeof(type) - sizeof(value);
    ValueInfo* info = NULL;
    intptr_t* ExtraValues = NULL;
    uint32_t ExtraValues_Count = 0;

    memcpy(&type, data, sizeof(type));
    data += sizeof(type);
    memcpy(&value, data, sizeof(value));
    data += sizeof(value);

    switch (type)
    {
        case IntelMetadataBufferTypeCameraSource:
        case IntelMetadataBufferTypeEncoder:
        case IntelMetadataBufferTypeUser:
        {
            if (extrasize >0 && extrasize < sizeof(ValueInfo))
                return IMB_INVAL_BUFFER;

            if (extrasize > sizeof(ValueInfo)) //has extravalues
            {
                if ( (extrasize - sizeof(ValueInfo)) % sizeof(mValue) != 0 )
                    return IMB_INVAL_BUFFER;
                ExtraValues_Count = (extrasize - sizeof(ValueInfo)) / sizeof(mValue);
            }

            if (extrasize > 0)
            {
                info = new ValueInfo;
                memcpy(info, data, sizeof(ValueInfo));
                data += sizeof(ValueInfo);
            }

            if (ExtraValues_Count > 0)
            {
                ExtraValues = new intptr_t[ExtraValues_Count];
                memcpy(ExtraValues, data, ExtraValues_Count * sizeof(mValue));
            }

            break;
        }
        case IntelMetadataBufferTypeGrallocSource:
            if (extrasize > 0)
                return IMB_INVAL_BUFFER;

            break;
        default:
            return IMB_INVAL_BUFFER;
    }

    //store data
    mType = type;
    mValue = value;
    if (mInfo)
        delete mInfo;
    mInfo = info;
    if (mExtraValues)
        delete[] mExtraValues;
    mExtraValues = ExtraValues;
    mExtraValues_Count = ExtraValues_Count;
#ifdef INTEL_VIDEO_XPROC_SHARING
    if (mInfo != NULL)
        mSessionFlag = mInfo->sessionFlag;
#endif
    return IMB_SUCCESS;
}

IMB_Result IntelMetadataBuffer::Serialize(uint8_t* &data, uint32_t& size)
{
    if (mBytes == NULL)
    {
        if (mType == IntelMetadataBufferTypeGrallocSource && mInfo)
            return IMB_INVAL_PARAM;

        //assemble bytes according members
        mSize = sizeof(mType) + sizeof(mValue);
        if (mInfo)
        {
            mSize += sizeof(ValueInfo);
            if (mExtraValues)
                mSize += sizeof(mValue) * mExtraValues_Count;
        }

        mBytes = new uint8_t[mSize];
        uint8_t *ptr = mBytes;
        memcpy(ptr, &mType, sizeof(mType));
        ptr += sizeof(mType);
        memcpy(ptr, &mValue, sizeof(mValue));
        ptr += sizeof(mValue);

        if (mInfo)
        {
        #ifdef INTEL_VIDEO_XPROC_SHARING
            mInfo->sessionFlag = mSessionFlag;
        #endif
            memcpy(ptr, mInfo, sizeof(ValueInfo));
            ptr += sizeof(ValueInfo);

            if (mExtraValues)
                memcpy(ptr, mExtraValues, mExtraValues_Count * sizeof(mValue));
        }
    }

    data = mBytes;
    size = mSize;

    return IMB_SUCCESS;
}

uint32_t IntelMetadataBuffer::GetMaxBufferSize()
{
    return 256;
}

#ifdef INTEL_VIDEO_XPROC_SHARING
IMB_Result IntelMetadataBuffer::GetSessionFlag(uint32_t& sessionflag)
{
    sessionflag = mSessionFlag;

    return IMB_SUCCESS;
}

IMB_Result IntelMetadataBuffer::SetSessionFlag(uint32_t sessionflag)
{
    mSessionFlag = sessionflag;

    return IMB_SUCCESS;
}

IMB_Result IntelMetadataBuffer::ShareValue(sp<MemoryBase> mem)
{
    mValue = (intptr_t)((intptr_t) ( mem->pointer() + 0x0FFF) & ~0x0FFF);

    if ( !(mSessionFlag & REMOTE_PROVIDER) && !(mSessionFlag & REMOTE_CONSUMER)) //no sharing
        return IMB_SUCCESS;

    if (mSessionFlag & REMOTE_PROVIDER) //is remote provider
    {
        sp<IBinder> binder = GetIntelBufferSharingService();
        if (binder == 0)
            return IMB_NO_SERVICE;

        //Detect IntelBufferSharingService, share mem to service
        Parcel data, reply;

        //send pid, sessionflag, and value
        pid_t pid = getpid();
        //TODO: if pid is int32?
        data.writeInt32(pid);
        data.writeInt32(mSessionFlag);
        data.writeIntPtr(mValue);

        //send type/obj (offset/size/MemHeap)
        ShareMemMap smem;
        smem.membase = mem;
        smem.type = ST_MEMBASE;
        if (WriteMemObjToBinder(data, &smem) != NO_ERROR)
            return IMB_SERVICE_FAIL;

        //do transcation
        if (binder->transact(SHARE_MEM, data, &reply) != NO_ERROR)
            return IMB_SERVICE_FAIL;

        //set new value gotten from peer
        mValue = reply.readIntPtr();
//        LOGI("ShareValue(membase) Get reply from sevice, new value:%x\n", mValue);
    }
    else  //is local provider , direct access list
    {
        ShareMemMap* smem = new ShareMemMap;
        smem->sessionflag = mSessionFlag;
        smem->value = mValue;
        smem->value_backup = mValue;
        smem->type = ST_MEMBASE;
        smem->membase = mem;
        smem->gbuffer = NULL;
        PushShareMem(smem);
    }

    return IMB_SUCCESS;
}

IMB_Result IntelMetadataBuffer::ShareValue(sp<GraphicBuffer> gbuffer)
{
    mValue = (intptr_t)gbuffer->handle;

    if ( !(mSessionFlag & REMOTE_PROVIDER) && !(mSessionFlag & REMOTE_CONSUMER)) //no sharing
        return IMB_SUCCESS;

    if (mSessionFlag & REMOTE_PROVIDER == 0) //is remote provider
    {
        sp<IBinder> binder = GetIntelBufferSharingService();
        if (binder == 0)
            return IMB_NO_SERVICE;

        Parcel data, reply;

        //send pid, sessionflag, and memtype
        pid_t pid = getpid();
        //TODO: if pid is int32 ?
        data.writeInt32(pid);
        data.writeInt32(mSessionFlag);
        data.writeIntPtr(mValue);

        //send value/graphicbuffer obj
        ShareMemMap smem;
        smem.gbuffer = gbuffer;
        smem.type = ST_GFX;
        if (WriteMemObjToBinder(data, &smem) != NO_ERROR)
            return IMB_SERVICE_FAIL;

        //do transcation
        if (binder->transact(SHARE_MEM, data, &reply) != NO_ERROR)
            return IMB_SERVICE_FAIL;

        //set new value gotten from peer
        mValue = reply.readIntPtr();
//        LOGI("ShareValue(gfx) Get reply from sevice, new value:%x\n", mValue);
    }
    else //is local provider, direct access list
    {
        ShareMemMap* smem = new ShareMemMap;
        smem->sessionflag = mSessionFlag;
        smem->value = mValue;
        smem->value_backup = mValue;
        smem->type = ST_GFX;
        smem->membase = NULL;
        smem->gbuffer = gbuffer;
        PushShareMem(smem);
    }

    return IMB_SUCCESS;
}

IMB_Result IntelMetadataBuffer::ClearContext(uint32_t sessionflag, bool isProvider)
{
    if ( !(sessionflag & REMOTE_PROVIDER) && !(sessionflag & REMOTE_CONSUMER)) //no sharing
        return IMB_SUCCESS;

    //clear local firstly
    ClearLocalMem(sessionflag);

    //clear mem on service if it is remote user
    if ((isProvider && (sessionflag & REMOTE_PROVIDER)) || (!isProvider && (sessionflag & REMOTE_CONSUMER)))
    {
//        LOGI("CLEAR_MEM sessionflag=%x", sessionflag);

        sp<IBinder> binder = GetIntelBufferSharingService();
        if (binder == 0)
            return IMB_NO_SERVICE;

        //Detect IntelBufferSharingService, unshare mem from service
        Parcel data, reply;

        //send pid and sessionflag
        pid_t pid = getpid();
        //TODO: if pid is int32?
        data.writeInt32(pid);
        data.writeInt32(sessionflag);

        if (binder->transact(CLEAR_MEM, data, &reply) != NO_ERROR)
            return IMB_SERVICE_FAIL;
    }

    return IMB_SUCCESS;
}

uint32_t IntelMetadataBuffer::MakeSessionFlag(bool romoteProvider, bool remoteConsumer, uint16_t sindex)
{
    uint32_t sessionflag = 0;

    if (romoteProvider)
        sessionflag |= REMOTE_PROVIDER;

    if (remoteConsumer)
        sessionflag |= REMOTE_CONSUMER;

    return sessionflag + sindex;
}
#endif