/*
 * Copyright (C) 2011 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 "rsContext.h"

using namespace android;
using namespace android::renderscript;

Mesh::Mesh(Context *rsc) : ObjectBase(rsc) {
    mHal.drv = NULL;
    mHal.state.primitives = NULL;
    mHal.state.primitivesCount = 0;
    mHal.state.vertexBuffers = NULL;
    mHal.state.vertexBuffersCount = 0;
    mInitialized = false;
}

Mesh::Mesh(Context *rsc,
           uint32_t vertexBuffersCount,
           uint32_t primitivesCount) : ObjectBase(rsc) {
    mHal.drv = NULL;
    mHal.state.primitivesCount = primitivesCount;
    mHal.state.primitives = new Primitive_t *[mHal.state.primitivesCount];
    for (uint32_t i = 0; i < mHal.state.primitivesCount; i ++) {
        mHal.state.primitives[i] = new Primitive_t;
    }
    mHal.state.vertexBuffersCount = vertexBuffersCount;
    mHal.state.vertexBuffers = new ObjectBaseRef<Allocation>[mHal.state.vertexBuffersCount];
}

Mesh::~Mesh() {
#ifndef ANDROID_RS_SERIALIZE
    mRSC->mHal.funcs.mesh.destroy(mRSC, this);
#endif

    if (mHal.state.vertexBuffers) {
        delete[] mHal.state.vertexBuffers;
    }

    if (mHal.state.primitives) {
        for (uint32_t i = 0; i < mHal.state.primitivesCount; i ++) {
            mHal.state.primitives[i]->mIndexBuffer.clear();
            delete mHal.state.primitives[i];
        }
        delete[] mHal.state.primitives;
    }
}

void Mesh::init() {
#ifndef ANDROID_RS_SERIALIZE
    mRSC->mHal.funcs.mesh.init(mRSC, this);
#endif
}

void Mesh::serialize(OStream *stream) const {
    // Need to identify ourselves
    stream->addU32((uint32_t)getClassId());

    String8 name(getName());
    stream->addString(&name);

    // Store number of vertex streams
    stream->addU32(mHal.state.vertexBuffersCount);
    for (uint32_t vCount = 0; vCount < mHal.state.vertexBuffersCount; vCount ++) {
        mHal.state.vertexBuffers[vCount]->serialize(stream);
    }

    stream->addU32(mHal.state.primitivesCount);
    // Store the primitives
    for (uint32_t pCount = 0; pCount < mHal.state.primitivesCount; pCount ++) {
        Primitive_t * prim = mHal.state.primitives[pCount];

        stream->addU8((uint8_t)prim->mPrimitive);

        if (prim->mIndexBuffer.get()) {
            stream->addU32(1);
            prim->mIndexBuffer->serialize(stream);
        } else {
            stream->addU32(0);
        }
    }
}

Mesh *Mesh::createFromStream(Context *rsc, IStream *stream) {
    // First make sure we are reading the correct object
    RsA3DClassID classID = (RsA3DClassID)stream->loadU32();
    if (classID != RS_A3D_CLASS_ID_MESH) {
        LOGE("mesh loading skipped due to invalid class id");
        return NULL;
    }

    String8 name;
    stream->loadString(&name);

    uint32_t vertexBuffersCount = stream->loadU32();
    ObjectBaseRef<Allocation> *vertexBuffers = NULL;
    if (vertexBuffersCount) {
        vertexBuffers = new ObjectBaseRef<Allocation>[vertexBuffersCount];

        for (uint32_t vCount = 0; vCount < vertexBuffersCount; vCount ++) {
            Allocation *vertexAlloc = Allocation::createFromStream(rsc, stream);
            vertexBuffers[vCount].set(vertexAlloc);
        }
    }

    uint32_t primitivesCount = stream->loadU32();
    ObjectBaseRef<Allocation> *indexBuffers = NULL;
    RsPrimitive *primitives = NULL;
    if (primitivesCount) {
        indexBuffers = new ObjectBaseRef<Allocation>[primitivesCount];
        primitives = new RsPrimitive[primitivesCount];

        // load all primitives
        for (uint32_t pCount = 0; pCount < primitivesCount; pCount ++) {
            primitives[pCount] = (RsPrimitive)stream->loadU8();

            // Check to see if the index buffer was stored
            uint32_t isIndexPresent = stream->loadU32();
            if (isIndexPresent) {
                Allocation *indexAlloc = Allocation::createFromStream(rsc, stream);
                indexBuffers[pCount].set(indexAlloc);
            }
        }
    }

    Mesh *mesh = new Mesh(rsc, vertexBuffersCount, primitivesCount);
    mesh->setName(name.string(), name.size());
    for (uint32_t vCount = 0; vCount < vertexBuffersCount; vCount ++) {
        mesh->setVertexBuffer(vertexBuffers[vCount].get(), vCount);
    }
    for (uint32_t pCount = 0; pCount < primitivesCount; pCount ++) {
        mesh->setPrimitive(indexBuffers[pCount].get(), primitives[pCount], pCount);
    }

    // Cleanup
    if (vertexBuffersCount) {
        delete[] vertexBuffers;
    }
    if (primitivesCount) {
        delete[] indexBuffers;
        delete[] primitives;
    }

#ifndef ANDROID_RS_SERIALIZE
    mesh->init();
    mesh->uploadAll(rsc);
#endif
    return mesh;
}

void Mesh::render(Context *rsc) const {
    for (uint32_t ct = 0; ct < mHal.state.primitivesCount; ct ++) {
        renderPrimitive(rsc, ct);
    }
}

void Mesh::renderPrimitive(Context *rsc, uint32_t primIndex) const {
    if (primIndex >= mHal.state.primitivesCount) {
        LOGE("Invalid primitive index");
        return;
    }

    Primitive_t *prim = mHal.state.primitives[primIndex];

    if (prim->mIndexBuffer.get()) {
        renderPrimitiveRange(rsc, primIndex, 0, prim->mIndexBuffer->getType()->getDimX());
        return;
    }

    renderPrimitiveRange(rsc, primIndex, 0, mHal.state.vertexBuffers[0]->getType()->getDimX());
}

void Mesh::renderPrimitiveRange(Context *rsc, uint32_t primIndex, uint32_t start, uint32_t len) const {
    if (len < 1 || primIndex >= mHal.state.primitivesCount) {
        LOGE("Invalid mesh or parameters");
        return;
    }

    mRSC->mHal.funcs.mesh.draw(mRSC, this, primIndex, start, len);
}

void Mesh::uploadAll(Context *rsc) {
    for (uint32_t ct = 0; ct < mHal.state.vertexBuffersCount; ct ++) {
        if (mHal.state.vertexBuffers[ct].get()) {
            rsc->mHal.funcs.allocation.markDirty(rsc, mHal.state.vertexBuffers[ct].get());
        }
    }

    for (uint32_t ct = 0; ct < mHal.state.primitivesCount; ct ++) {
        if (mHal.state.primitives[ct]->mIndexBuffer.get()) {
            rsc->mHal.funcs.allocation.markDirty(rsc, mHal.state.primitives[ct]->mIndexBuffer.get());
        }
    }
}

void Mesh::computeBBox() {
    float *posPtr = NULL;
    uint32_t vectorSize = 0;
    uint32_t stride = 0;
    uint32_t numVerts = 0;
    // First we need to find the position ptr and stride
    for (uint32_t ct=0; ct < mHal.state.vertexBuffersCount; ct++) {
        const Type *bufferType = mHal.state.vertexBuffers[ct]->getType();
        const Element *bufferElem = bufferType->getElement();

        for (uint32_t ct=0; ct < bufferElem->getFieldCount(); ct++) {
            if (strcmp(bufferElem->getFieldName(ct), "position") == 0) {
                vectorSize = bufferElem->getField(ct)->getComponent().getVectorSize();
                stride = bufferElem->getSizeBytes() / sizeof(float);
                uint32_t offset = bufferElem->getFieldOffsetBytes(ct);
                posPtr = (float*)((uint8_t*)mHal.state.vertexBuffers[ct]->getPtr() + offset);
                numVerts = bufferType->getDimX();
                break;
            }
        }
        if (posPtr) {
            break;
        }
    }

    mBBoxMin[0] = mBBoxMin[1] = mBBoxMin[2] = 1e6;
    mBBoxMax[0] = mBBoxMax[1] = mBBoxMax[2] = -1e6;
    if (!posPtr) {
        LOGE("Unable to compute bounding box");
        mBBoxMin[0] = mBBoxMin[1] = mBBoxMin[2] = 0.0f;
        mBBoxMax[0] = mBBoxMax[1] = mBBoxMax[2] = 0.0f;
        return;
    }

    for (uint32_t i = 0; i < numVerts; i ++) {
        for (uint32_t v = 0; v < vectorSize; v ++) {
            mBBoxMin[v] = rsMin(mBBoxMin[v], posPtr[v]);
            mBBoxMax[v] = rsMax(mBBoxMax[v], posPtr[v]);
        }
        posPtr += stride;
    }
}

namespace android {
namespace renderscript {

RsMesh rsi_MeshCreate(Context *rsc,
                      RsAllocation * vtx, size_t vtxCount,
                      RsAllocation * idx, size_t idxCount,
                      uint32_t * primType, size_t primTypeCount) {
    rsAssert(idxCount == primTypeCount);
    Mesh *sm = new Mesh(rsc, vtxCount, idxCount);
    sm->incUserRef();

    for (uint32_t i = 0; i < vtxCount; i ++) {
        sm->setVertexBuffer((Allocation*)vtx[i], i);
    }

    for (uint32_t i = 0; i < idxCount; i ++) {
        sm->setPrimitive((Allocation*)idx[i], (RsPrimitive)primType[i], i);
    }

    sm->init();

    return sm;
}

}}

void rsaMeshGetVertexBufferCount(RsContext con, RsMesh mv, int32_t *numVtx) {
    Mesh *sm = static_cast<Mesh *>(mv);
    *numVtx = sm->mHal.state.vertexBuffersCount;
}

void rsaMeshGetIndexCount(RsContext con, RsMesh mv, int32_t *numIdx) {
    Mesh *sm = static_cast<Mesh *>(mv);
    *numIdx = sm->mHal.state.primitivesCount;
}

void rsaMeshGetVertices(RsContext con, RsMesh mv, RsAllocation *vtxData, uint32_t vtxDataCount) {
    Mesh *sm = static_cast<Mesh *>(mv);
    rsAssert(vtxDataCount == sm->mHal.state.vertexBuffersCount);

    for (uint32_t ct = 0; ct < vtxDataCount; ct ++) {
        vtxData[ct] = sm->mHal.state.vertexBuffers[ct].get();
        sm->mHal.state.vertexBuffers[ct]->incUserRef();
    }
}

void rsaMeshGetIndices(RsContext con, RsMesh mv, RsAllocation *va, uint32_t *primType, uint32_t idxDataCount) {
    Mesh *sm = static_cast<Mesh *>(mv);
    rsAssert(idxDataCount == sm->mHal.state.primitivesCount);

    for (uint32_t ct = 0; ct < idxDataCount; ct ++) {
        va[ct] = sm->mHal.state.primitives[ct]->mIndexBuffer.get();
        primType[ct] = sm->mHal.state.primitives[ct]->mPrimitive;
        if (sm->mHal.state.primitives[ct]->mIndexBuffer.get()) {
            sm->mHal.state.primitives[ct]->mIndexBuffer->incUserRef();
        }
    }
}