// Copyright 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/address_space_host_media.h"
#include "host-common/vm_operations.h"
#include "aemu/base/AlignedBuf.h"

#define AS_DEVICE_DEBUG 0

#if AS_DEVICE_DEBUG
#define AS_DEVICE_DPRINT(fmt,...) fprintf(stderr, "%s:%d " fmt "\n", __func__, __LINE__, ##__VA_ARGS__);
#else
#define AS_DEVICE_DPRINT(fmt,...)
#endif

namespace android {
namespace emulation {

enum class DecoderType : uint8_t {
    Vpx = 0,
    H264 = 1,
};

AddressSpaceHostMediaContext::AddressSpaceHostMediaContext(
    const struct AddressSpaceCreateInfo& create, const address_space_device_control_ops* ops)
    : mControlOps(ops) {
    // The memory is allocated in the snapshot load if called from a snapshot load().
    if (!create.fromSnapshot) {
        mGuestAddr = create.physAddr;
        allocatePages(create.physAddr, kNumPages);
    }
}

AddressSpaceHostMediaContext::~AddressSpaceHostMediaContext() {
    deallocatePages(mGuestAddr, kNumPages);
}

void AddressSpaceHostMediaContext::perform(AddressSpaceDevicePingInfo *info) {
    handleMediaRequest(info);
}

AddressSpaceDeviceType AddressSpaceHostMediaContext::getDeviceType() const {
    return AddressSpaceDeviceType::Media;
}

void AddressSpaceHostMediaContext::save(base::Stream* stream) const {
    AS_DEVICE_DPRINT("Saving Host Media snapshot");
    stream->putBe64(mGuestAddr);
    int numActiveDecoders = 0;
    if (mVpxDecoder != nullptr) {
        ++ numActiveDecoders;
    }
    if (mH264Decoder != nullptr) {
        ++ numActiveDecoders;
    }

    stream->putBe32(numActiveDecoders);
    if (mVpxDecoder != nullptr) {
        AS_DEVICE_DPRINT("Saving VpxDecoder snapshot");
        stream->putBe32((uint32_t)DecoderType::Vpx);
        mVpxDecoder->save(stream);
    }
    if (mH264Decoder != nullptr) {
        AS_DEVICE_DPRINT("Saving H264Decoder snapshot");
        stream->putBe32((uint32_t)DecoderType::H264);
        mH264Decoder->save(stream);
    }
}

bool AddressSpaceHostMediaContext::load(base::Stream* stream) {
    deallocatePages(mGuestAddr, kNumPages);
    AS_DEVICE_DPRINT("Loading Host Media snapshot");
    mGuestAddr = stream->getBe64();
    allocatePages(mGuestAddr, kNumPages);

    int numActiveDecoders = stream->getBe32();
    for (int i = 0; i < numActiveDecoders; ++i) {
        stream->getBe32();
        // TODO: Add support for virtio-gpu-as-video-decode
        // switch (t) {
        // case DecoderType::Vpx:
        //     AS_DEVICE_DPRINT("Loading VpxDecoder snapshot");
        //     mVpxDecoder.reset(new MediaVpxDecoder);
        //     mVpxDecoder->load(stream);
        //     break;
        // case DecoderType::H264:
        //     AS_DEVICE_DPRINT("Loading H264Decoder snapshot");
        //     mH264Decoder.reset(MediaH264Decoder::create());
        //     mH264Decoder->load(stream);
        //     break;
        // default:
        //     break;
        // }
    }
    return true;
}

void AddressSpaceHostMediaContext::allocatePages(uint64_t phys_addr, int num_pages) {
    mHostBuffer = android::aligned_buf_alloc(kAlignment, num_pages * 4096);
    mControlOps->add_memory_mapping(
        phys_addr, mHostBuffer, num_pages * 4096);
    AS_DEVICE_DPRINT("Allocating host memory for media context: guest_addr 0x%" PRIx64 ", 0x%" PRIx64,
                     (uint64_t)phys_addr, (uint64_t)mHostBuffer);
}

void AddressSpaceHostMediaContext::deallocatePages(uint64_t phys_addr,
                                                   int num_pages) {
    if (mHostBuffer == nullptr) {
        return;
    }

    mControlOps->remove_memory_mapping(phys_addr, mHostBuffer,
                                       num_pages * 4096);
    android::aligned_buf_free(mHostBuffer);
    mHostBuffer = nullptr;
    AS_DEVICE_DPRINT(
            "De-Allocating host memory for media context: guest_addr 0x%" PRIx64
            ", 0x%" PRIx64,
            (uint64_t)phys_addr, (uint64_t)mHostBuffer);
}

// static
MediaCodecType AddressSpaceHostMediaContext::getMediaCodecType(uint64_t metadata) {
    // Metadata has the following structure:
    // - Upper 8 bits has the codec type (MediaCodecType)
    // - Lower 56 bits has metadata specifically for that codec
    //
    // We need to hand the data off to the right codec depending on which
    // codec type we get.
    uint8_t ret = (uint8_t)(metadata >> (64 - 8));
    return ret > static_cast<uint8_t>(MediaCodecType::Max) ?
            MediaCodecType::Max : (MediaCodecType)ret;
}

// static
MediaOperation AddressSpaceHostMediaContext::getMediaOperation(uint64_t metadata) {
    // Metadata has the following structure:
    // - Upper 8 bits has the codec type (MediaCodecType)
    // - Lower 56 bits has metadata specifically for that codec
    //
    // We need to hand the data off to the right codec depending on which
    // codec type we get.
    uint8_t ret = (uint8_t)(metadata & 0xFF);
    return ret > static_cast<uint8_t>(MediaOperation::Max) ?
            MediaOperation::Max : (MediaOperation)ret;
}

static uint64_t getAddrSlot(uint64_t metadata) {
    uint64_t ret = metadata << 8;  // get rid of typecode
    ret = ret >> 16;               // get rid of opcode
    return ret;
}

void AddressSpaceHostMediaContext::handleMediaRequest(AddressSpaceDevicePingInfo *info) {
    auto codecType = getMediaCodecType(info->metadata);
    auto op = getMediaOperation(info->metadata);
    auto slot = getAddrSlot(info->metadata);
    uint64_t offSetAddr = slot << 20;

    AS_DEVICE_DPRINT("Got media request (type=%u, op=%u, slot=%lld)",
                     static_cast<uint8_t>(codecType), static_cast<uint8_t>(op),
                     (long long)(getAddrSlot(info->metadata)));

    switch (codecType) {
        case MediaCodecType::VP8Codec:
        case MediaCodecType::VP9Codec:
            // if (!mVpxDecoder) {
            //     mVpxDecoder.reset(new MediaVpxDecoder);
            // }
            mVpxDecoder->handlePing(
                    codecType, op,
                    (uint8_t*)(mControlOps->get_host_ptr(info->phys_addr)) +
                            offSetAddr);
            break;
        case MediaCodecType::H264Codec:
            // if (!mH264Decoder) {
            //     mH264Decoder.reset(MediaH264Decoder::create());
            // }
            mH264Decoder->handlePing(
                    codecType, op,
                    (uint8_t*)(mControlOps->get_host_ptr(info->phys_addr)) +
                            offSetAddr);
            break;
        default:
            AS_DEVICE_DPRINT("codec type %d not implemented", (int)codecType);
            break;
    }
}

}  // namespace emulation
}  // namespace android
