#include "BinaryDataParser.h"

DRAGONBONES_NAMESPACE_BEGIN

TimelineData* BinaryDataParser::_parseBinaryTimeline(TimelineType type, unsigned offset, TimelineData* timelineData) {
    const auto timeline = timelineData != nullptr ? timelineData : BaseObject::borrowObject<TimelineData>();
    timeline->type = type;
    timeline->offset = offset;

    _timeline = timeline;

    const auto keyFrameCount = (unsigned)_timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineKeyFrameCount];
    if (keyFrameCount == 1) {
        timeline->frameIndicesOffset = -1;
    } else {
        unsigned frameIndicesOffset = 0;
        const auto totalFrameCount = _animation->frameCount + 1; // One more frame than animation.
        auto& frameIndices = _data->frameIndices;

        frameIndicesOffset = frameIndices.size();
        timeline->frameIndicesOffset = frameIndicesOffset;
        frameIndices.resize(frameIndicesOffset + totalFrameCount);

        for (
            std::size_t i = 0, iK = 0, frameStart = 0, frameCount = 0;
            i < totalFrameCount;
            ++i) {
            if (frameStart + frameCount <= i && iK < keyFrameCount) {
                frameStart = _frameArray[_animation->frameOffset + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineFrameOffset + iK]];
                if (iK == keyFrameCount - 1) {
                    frameCount = _animation->frameCount - frameStart;
                } else {
                    frameCount = _frameArray[_animation->frameOffset + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineFrameOffset + iK + 1]] - frameStart;
                }

                iK++;
            }

            frameIndices[frameIndicesOffset + i] = iK - 1;
        }
    }

    _timeline = nullptr;

    return timeline;
}

void BinaryDataParser::_parseVertices(const rapidjson::Value& rawData, VerticesData& vertices) {
    vertices.offset = rawData[OFFSET].GetUint();

    const auto weightOffset = _intArray[vertices.offset + (unsigned)BinaryOffset::MeshWeightOffset];
    if (weightOffset >= 0) {
        const auto weight = BaseObject::borrowObject<WeightData>();
        const auto vertexCount = _intArray[vertices.offset + (unsigned)BinaryOffset::MeshVertexCount];
        const auto boneCount = (unsigned)_intArray[weightOffset + (unsigned)BinaryOffset::WeigthBoneCount];
        weight->offset = weightOffset;

        for (std::size_t i = 0; i < boneCount; ++i) {
            const auto boneIndex = _intArray[weightOffset + (unsigned)BinaryOffset::WeigthBoneIndices + i];
            weight->addBone(_rawBones[boneIndex]);
        }

        auto boneIndicesOffset = weightOffset + (unsigned)BinaryOffset::WeigthBoneIndices + boneCount;
        unsigned weightCount = 0;
        for (std::size_t i = 0, l = vertexCount; i < l; ++i) {
            const auto vertexBoneCount = (unsigned)_intArray[boneIndicesOffset++];
            weightCount += vertexBoneCount;
            boneIndicesOffset += vertexBoneCount;
        }

        weight->count = weightCount;
        vertices.weight = weight;
    }
}

void BinaryDataParser::_parseMesh(const rapidjson::Value& rawData, MeshDisplayData& mesh) {
    _parseVertices(rawData, mesh.vertices);
}

AnimationData* BinaryDataParser::_parseAnimation(const rapidjson::Value& rawData) {
    const auto animation = BaseObject::borrowObject<AnimationData>();
    animation->frameCount = std::max(_getNumber(rawData, DURATION, 1), 1);
    animation->playTimes = _getNumber(rawData, PLAY_TIMES, 1);
    animation->duration = (float)(animation->frameCount) / _armature->frameRate; // float
    animation->fadeInTime = _getNumber(rawData, FADE_IN_TIME, 0.0f);
    animation->scale = _getNumber(rawData, SCALE, 1.0f);
    animation->name = _getString(rawData, NAME, DEFAULT_NAME);
    if (animation->name.empty()) {
        animation->name = DEFAULT_NAME;
    }

    // Offsets.
    const auto& offsets = rawData[OFFSET];
    animation->frameIntOffset = offsets[0].GetUint();
    animation->frameFloatOffset = offsets[1].GetUint();
    animation->frameOffset = offsets[2].GetUint();

    _animation = animation;

    if (rawData.HasMember(ACTION)) {
        animation->actionTimeline = _parseBinaryTimeline(TimelineType::Action, rawData[ACTION].GetUint());
    }

    if (rawData.HasMember(Z_ORDER)) {
        animation->zOrderTimeline = _parseBinaryTimeline(TimelineType::ZOrder, rawData[Z_ORDER].GetUint());
    }

    if (rawData.HasMember(BONE)) {
        const auto& rawTimeliness = rawData[BONE];
        for (auto iterator = rawTimeliness.MemberBegin(); iterator != rawTimeliness.MemberEnd(); ++iterator) {
            const auto bone = _armature->getBone(iterator->name.GetString());
            if (bone == nullptr) {
                continue;
            }

            const auto& rawTimelines = *&(iterator->value);
            for (std::size_t i = 0, l = rawTimelines.Size(); i < l; i += 2) {
                const auto timelineType = (TimelineType)rawTimelines[i].GetInt();
                const auto timelineOffset = rawTimelines[i + 1].GetUint();
                const auto timeline = _parseBinaryTimeline(timelineType, timelineOffset);
                _animation->addBoneTimeline(bone, timeline);
            }
        }
    }

    if (rawData.HasMember(SLOT)) {
        const auto& rawTimeliness = rawData[SLOT];
        for (auto iterator = rawTimeliness.MemberBegin(); iterator != rawTimeliness.MemberEnd(); ++iterator) {
            const auto slot = _armature->getSlot(iterator->name.GetString());
            if (slot == nullptr) {
                continue;
            }

            const auto& rawTimelines = *&(iterator->value);
            for (std::size_t i = 0, l = rawTimelines.Size(); i < l; i += 2) {
                const auto timelineType = (TimelineType)rawTimelines[i].GetInt();
                const auto timelineOffset = rawTimelines[i + 1].GetUint();
                const auto timeline = _parseBinaryTimeline(timelineType, timelineOffset);
                _animation->addSlotTimeline(slot, timeline);
            }
        }
    }

    if (rawData.HasMember(CONSTRAINT)) {
        const auto& rawTimeliness = rawData[CONSTRAINT];
        for (auto iterator = rawTimeliness.MemberBegin(); iterator != rawTimeliness.MemberEnd(); ++iterator) {
            const auto constraint = _armature->getConstraint(iterator->name.GetString());
            if (constraint == nullptr) {
                continue;
            }

            const auto& rawTimelines = *&(iterator->value);
            for (std::size_t i = 0, l = rawTimelines.Size(); i < l; i += 2) {
                const auto timelineType = (TimelineType)rawTimelines[i].GetInt();
                const auto timelineOffset = rawTimelines[i + 1].GetUint();
                const auto timeline = _parseBinaryTimeline(timelineType, timelineOffset);
                _animation->addConstraintTimeline(constraint, timeline);
            }
        }
    }

    _animation = nullptr;

    return animation;
}

void BinaryDataParser::_parseArray(const rapidjson::Value& rawData) {
    const auto& offsets = rawData[OFFSET];

    _data->binary = _binary;
    _data->intArray = _intArray = (int16_t*)(_binary + _binaryOffset + offsets[0].GetUint());
    _data->floatArray = _floatArray = (float*)(_binary + _binaryOffset + offsets[2].GetUint());
    _data->frameIntArray = _frameIntArray = (int16_t*)(_binary + _binaryOffset + offsets[4].GetUint());
    _data->frameFloatArray = _frameFloatArray = (float*)(_binary + _binaryOffset + offsets[6].GetUint());
    _data->frameArray = _frameArray = (int16_t*)(_binary + _binaryOffset + offsets[8].GetUint());
    _data->timelineArray = _timelineArray = (uint16_t*)(_binary + _binaryOffset + offsets[10].GetUint());
}

DragonBonesData* BinaryDataParser::parseDragonBonesData(const char* rawData, float scale) {
    DRAGONBONES_ASSERT(rawData != nullptr, "");

    if (
        rawData[0] != 'D' ||
        rawData[1] != 'B' ||
        rawData[2] != 'D' ||
        rawData[3] != 'T') {
        DRAGONBONES_ASSERT(false, "Nonsupport data.");
        return nullptr;
    }

    const auto headerLength = (std::size_t)(((uint32_t*)(rawData + 8))[0]);
    const auto headerBytes = rawData + 8 + 4;
    rapidjson::Document document;
    document.Parse(headerBytes, headerLength);

    _binaryOffset = 8 + 4 + headerLength;
    _binary = rawData;

    return JSONDataParser::_parseDragonBonesData(document, scale);
}

DRAGONBONES_NAMESPACE_END
