// Copyright (C) 2019 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 "host-common/MediaH264DecoderDefault.h"
#include "base/System.h"
#include "host-common/H264PingInfoParser.h"
#include "host-common/MediaH264DecoderGeneric.h"

#include <cstdint>
#include <string>
#include <vector>

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

#define MEDIA_H264_DEBUG 0

#if MEDIA_H264_DEBUG
#define H264_DPRINT(fmt,...) fprintf(stderr, "h264-dec: %s:%d " fmt "\n", __func__, __LINE__, ##__VA_ARGS__);
#else
#define H264_DPRINT(fmt,...)
#endif

namespace android {
namespace emulation {

namespace {
MediaH264DecoderPlugin* makeDecoderPlugin(uint64_t pluginid,
                                          H264PingInfoParser parser) {
    return new MediaH264DecoderGeneric(pluginid, parser);
}

}; // anon namespace


uint64_t MediaH264DecoderDefault::readId(void* ptr) {
    if (nullptr == ptr)
        return 0;
    uint64_t key = H264PingInfoParser::parseHostDecoderId(ptr);
    return key;
}

MediaH264DecoderPlugin* MediaH264DecoderDefault::getDecoder(uint64_t key) {
    {
        std::lock_guard<std::mutex> g(mMapLock);
        auto iter = mDecoders.find(key);
        if (iter != mDecoders.end()) {
            return iter->second;
        }
    }
    H264_DPRINT("Error: cannot find decoder with key %" PRIx64 "", key);
    return nullptr;
}

uint64_t MediaH264DecoderDefault::createId() {
    std::lock_guard<std::mutex> g(mIdLock);
    return ++mId;
}

void MediaH264DecoderDefault::addDecoder(uint64_t key,
                                         MediaH264DecoderPlugin* val) {
    {
        std::lock_guard<std::mutex> g(mMapLock);
        if (mDecoders.find(key) == mDecoders.end()) {
            mDecoders[key] = val;
            H264_DPRINT("added decoder key %" PRIx64 " val: %p", key, val);
            return;
        }
    }
    H264_DPRINT("cannot add: already exist");
}

void MediaH264DecoderDefault::updateDecoder(uint64_t key,
                                            MediaH264DecoderPlugin* val) {
    std::lock_guard<std::mutex> g(mMapLock);
    if (mDecoders.find(key) == mDecoders.end()) {
        H264_DPRINT("error: decoder with key %" PRIx64 " does not exist", key);
    } else {
        mDecoders[key] = val;
        H264_DPRINT("updated key %" PRIx64 " with new decoder %p", key, val);
    }
}

void MediaH264DecoderDefault::removeDecoder(uint64_t key) {
    {
        std::lock_guard<std::mutex> g(mMapLock);
        auto iter = mDecoders.find(key);
        if (iter != mDecoders.end()) {
            H264_DPRINT("removed decoder key %" PRIx64 ", val: %p", key,
                        mDecoders[key]);
            mDecoders.erase(iter);
            return;
        }
    }
    H264_DPRINT("error: cannot remove decoder, not in map");
}

static void* getReturnAddress(void* ptr) {
    uint8_t* xptr = (uint8_t*)ptr;
    void* pint = (void*)(xptr + 256);
    return pint;
}


void MediaH264DecoderDefault::handlePing(MediaCodecType type,
                                         MediaOperation op,
                                         void* ptr) {
    using InitContextParam = H264PingInfoParser::InitContextParam;
    using DecodeFrameParam = H264PingInfoParser::DecodeFrameParam;
    using ResetParam = H264PingInfoParser::ResetParam;
    using GetImageParam = H264PingInfoParser::GetImageParam;

    switch (op) {
        case MediaOperation::InitContext: {
            H264PingInfoParser parser{ptr};
            InitContextParam param{};
            parser.parseInitContextParams(ptr, param);
            H264_DPRINT(
                    "handle init decoder context request from guest version %u",
                    parser.version());
            uint64_t myid = createId();
            MediaH264DecoderPlugin* mydecoder = makeDecoderPlugin(myid, parser);
            addDecoder(myid, mydecoder);
            mydecoder->initH264Context(ptr);
            *(param.pHostDecoderId) = myid;
            H264_DPRINT("done handling InitContext");
            break;
        }
        case MediaOperation::DestroyContext: {
            H264_DPRINT("handle destroy request from guest %p", ptr);
            MediaH264DecoderPlugin* mydecoder = getDecoder(readId(ptr));
            if (!mydecoder)
                return;

            delete mydecoder;
            removeDecoder(readId(ptr));
            break;
        }
        case MediaOperation::DecodeImage: {
            H264_DPRINT("handle decodeimage request from guest %p", ptr);
            MediaH264DecoderPlugin* mydecoder = getDecoder(readId(ptr));
            if (nullptr == mydecoder)
                return;
            mydecoder->decodeFrame(ptr);
            break;
        }
        case MediaOperation::Flush: {
            H264_DPRINT("handle flush request from guest %p", ptr);
            MediaH264DecoderPlugin* mydecoder = getDecoder(readId(ptr));
            if (nullptr == mydecoder)
                return;
            mydecoder->flush(ptr);
            break;
        }
        case MediaOperation::GetImage: {
            H264_DPRINT("handle getimage request from guest %p", ptr);
            MediaH264DecoderPlugin* mydecoder = getDecoder(readId(ptr));
            if (nullptr == mydecoder)
                return;
            mydecoder->getImage(ptr);
            break;
        }
        case MediaOperation::Reset: {
            H264_DPRINT("handle reset request from guest %p", ptr);
            uint64_t oldId = readId(ptr);
            MediaH264DecoderPlugin* olddecoder = getDecoder(oldId);
            if (nullptr == olddecoder) {
                H264_DPRINT("error, cannot reset on nullptr");
                return;
            }
            MediaH264DecoderPlugin* mydecoder = olddecoder->clone();
            delete olddecoder;
            mydecoder->reset(ptr);
            updateDecoder(oldId, mydecoder);
            break;
        }
        default:
            H264_DPRINT("Unknown command %u\n", (unsigned int)op);
            break;
    }
}

void MediaH264DecoderDefault::save(base::Stream* stream) const {
    stream->putBe64(mId);
    int size = mDecoders.size();
    stream->putBe32(size);
    for (auto item : mDecoders) {
        stream->putBe64(item.first);
        stream->putBe32(item.second->type());
        item.second->save(stream);
    }
}

bool MediaH264DecoderDefault::load(base::Stream* stream) {
    mId = stream->getBe64();
    int size = stream->getBe32();
    for (int i = 0; i < size; ++i) {
        // this is hacky; but we have to know the plugin type
        uint64_t id = stream->getBe64();
        int type = stream->getBe32();
        if (type == MediaH264DecoderPlugin::PLUGIN_TYPE_GENERIC) {
            MediaH264DecoderGeneric* decoder =
                    new MediaH264DecoderGeneric(id, H264PingInfoParser(100));
            decoder->load(stream);
            mDecoders[id] = decoder;
            continue;
        }
        fprintf(stderr, "Error, un-implemented %s %d\n", __func__, __LINE__);
        exit(1);
    }
    return true;
}

}  // namespace emulation
}  // namespace android