#include "Armature.h"
#include "../animation/Animation.h"
#include "../animation/WorldClock.h"
#include "../event/EventObject.h"
#include "../model/TextureAtlasData.h"
#include "../model/UserData.h"
#include "Bone.h"
#include "Constraint.h"
#include "IArmatureProxy.h"
#include "Slot.h"

DRAGONBONES_NAMESPACE_BEGIN

bool Armature::_onSortSlots(Slot* a, Slot* b) {
    return a->_zOrder < b->_zOrder ? true : false;
}

void Armature::_onClear() {
    if (_clock != nullptr) // Remove clock before slots clear.
    {
        _clock->remove(this);
    }

    for (const auto bone : _bones) {
        bone->returnToPool();
    }

    for (const auto slot : _slots) {
        slot->returnToPool();
    }

    for (const auto constraint : _constraints) {
        constraint->returnToPool();
    }

    for (const auto action : _actions) {
        action->returnToPool();
    }

    if (_animation != nullptr) {
        _animation->returnToPool();
    }

    if (_proxy != nullptr) {
        _proxy->dbClear();
    }

    if (_replaceTextureAtlasData != nullptr) {
        _replaceTextureAtlasData->returnToPool();
    }

    inheritAnimation = true;
    userData = nullptr;

    _debugDraw = false;
    _lockUpdate = false;
    _slotsDirty = false;
    _zOrderDirty = false;
    _flipX = false;
    _flipY = false;
    _cacheFrameIndex = -1;
    _bones.clear();
    _slots.clear();
    _constraints.clear();
    _actions.clear();
    _armatureData = nullptr;
    _animation = nullptr;
    _proxy = nullptr;
    _display = nullptr;
    _replaceTextureAtlasData = nullptr;
    _replacedTexture = nullptr;
    _dragonBones = nullptr;
    _clock = nullptr;
    _parent = nullptr;
}

void Armature::_sortZOrder(const int16_t* slotIndices, unsigned offset) {
    const auto& slotDatas = _armatureData->sortedSlots;
    const auto isOriginal = slotIndices == nullptr;

    if (_zOrderDirty || !isOriginal) {
        for (std::size_t i = 0, l = slotDatas.size(); i < l; ++i) {
            const auto slotIndex = isOriginal ? i : (std::size_t)slotIndices[offset + i];
            if (slotIndex < 0 || slotIndex >= l) {
                continue;
            }

            const auto slotData = slotDatas[slotIndex];
            const auto slot = getSlot(slotData->name);
            if (slot != nullptr) {
                slot->_setZorder(i);
            }
        }

        _slotsDirty = true;
        _zOrderDirty = !isOriginal;
    }
}

void Armature::_addBone(Bone* value) {
    if (std::find(_bones.begin(), _bones.end(), value) == _bones.end()) {
        _bones.push_back(value);
    }
}

void Armature::_addSlot(Slot* value) {
    if (std::find(_slots.begin(), _slots.end(), value) == _slots.end()) {
        _slots.push_back(value);
    }
}

void Armature::_addConstraint(Constraint* value) {
    if (std::find(_constraints.cbegin(), _constraints.cend(), value) == _constraints.cend()) {
        _constraints.push_back(value);
    }
}

void Armature::_bufferAction(EventObject* action, bool append) {
    if (std::find(_actions.cbegin(), _actions.cend(), action) == _actions.cend()) {
        if (append) {
            _actions.push_back(action);
        } else {
            _actions.insert(_actions.begin(), action);
        }
    }
}

void Armature::dispose() {
    if (_armatureData != nullptr) {
        _lockUpdate = true;
        _dragonBones->bufferObject(this);
    }
}

void Armature::init(ArmatureData* armatureData, IArmatureProxy* proxy, void* display, DragonBones* dragonBones) {
    if (_armatureData != nullptr) {
        return;
    }

    _armatureData = armatureData;
    _animation = BaseObject::borrowObject<Animation>();
    _proxy = proxy;
    _display = display;
    _dragonBones = dragonBones;

    _proxy->dbInit(this);
    _animation->init(this);
    _animation->setAnimations(_armatureData->animations);
}

void Armature::advanceTime(float passedTime) {
    if (_lockUpdate) {
        return;
    }

    if (_armatureData == nullptr) {
        DRAGONBONES_ASSERT(false, "The armature has been disposed.");
        return;
    } else if (_armatureData->parent == nullptr) {
        DRAGONBONES_ASSERT(false, "The armature data has been disposed.\nPlease make sure dispose armature before call factory.clear().");
        return;
    }

    const auto prevCacheFrameIndex = _cacheFrameIndex;

    // Update animation.
    _animation->advanceTime(passedTime);

    // Sort slots.
    if (_slotsDirty) {
        _slotsDirty = false;
        std::sort(_slots.begin(), _slots.end(), Armature::_onSortSlots);
    }

    // Update bones and slots.
    if (_cacheFrameIndex < 0 || _cacheFrameIndex != prevCacheFrameIndex) {
        for (const auto bone : _bones) {
            bone->update(_cacheFrameIndex);
        }

        for (const auto slot : _slots) {
            slot->update(_cacheFrameIndex);
        }
    }

    // Do actions.
    if (!_actions.empty()) {
        _lockUpdate = true;

        for (const auto action : _actions) {
            const auto actionData = action->actionData;

            if (actionData != nullptr) {
                if (actionData->type == ActionType::Play) {
                    if (action->slot != nullptr) {
                        const auto childArmature = action->slot->getChildArmature();
                        if (childArmature != nullptr) {
                            childArmature->getAnimation()->fadeIn(actionData->name);
                        }
                    } else if (action->bone != nullptr) {
                        for (const auto slot : getSlots()) {
                            if (slot->getParent() == action->bone) {
                                const auto childArmature = slot->getChildArmature();
                                if (childArmature != nullptr) {
                                    childArmature->getAnimation()->fadeIn(actionData->name);
                                }
                            }
                        }
                    } else {
                        _animation->fadeIn(actionData->name);
                    }
                }
            }

            action->returnToPool();
        }

        _actions.clear();
        _lockUpdate = false;
    }

    _proxy->dbUpdate();
}

void Armature::render() {
    if (_proxy) {
        _proxy->dbRender();
    }
}

void Armature::invalidUpdate(const std::string& boneName, bool updateSlot) {
    if (!boneName.empty()) {
        const auto bone = getBone(boneName);
        if (bone != nullptr) {
            bone->invalidUpdate();

            if (updateSlot) {
                for (const auto slot : _slots) {
                    if (slot->getParent() == bone) {
                        slot->invalidUpdate();
                    }
                }
            }
        }
    } else {
        for (const auto bone : _bones) {
            bone->invalidUpdate();
        }

        if (updateSlot) {
            for (const auto slot : _slots) {
                slot->invalidUpdate();
            }
        }
    }
}

Slot* Armature::containsPoint(float x, float y) const {
    for (const auto slot : _slots) {
        if (slot->containsPoint(x, y)) {
            return slot;
        }
    }

    return nullptr;
}

Slot* Armature::intersectsSegment(
    float xA, float yA, float xB, float yB,
    Point* intersectionPointA,
    Point* intersectionPointB,
    Point* normalRadians) const {
    const auto isV = xA == xB;
    auto dMin = 0.0f;
    auto dMax = 0.0f;
    auto intXA = 0.0f;
    auto intYA = 0.0f;
    auto intXB = 0.0f;
    auto intYB = 0.0f;
    auto intAN = 0.0f;
    auto intBN = 0.0f;
    Slot* intSlotA = nullptr;
    Slot* intSlotB = nullptr;

    for (const auto& slot : _slots) {
        auto intersectionCount = slot->intersectsSegment(xA, yA, xB, yB, intersectionPointA, intersectionPointB, normalRadians);
        if (intersectionCount > 0) {
            if (intersectionPointA != nullptr || intersectionPointB != nullptr) {
                if (intersectionPointA != nullptr) {
                    float d = isV ? intersectionPointA->y - yA : intersectionPointA->x - xA;
                    if (d < 0.0f) {
                        d = -d;
                    }

                    if (intSlotA == nullptr || d < dMin) {
                        dMin = d;
                        intXA = intersectionPointA->x;
                        intYA = intersectionPointA->y;
                        intSlotA = slot;

                        if (normalRadians) {
                            intAN = normalRadians->x;
                        }
                    }
                }

                if (intersectionPointB != nullptr) {
                    float d = intersectionPointB->x - xA;
                    if (d < 0.0f) {
                        d = -d;
                    }

                    if (intSlotB == nullptr || d > dMax) {
                        dMax = d;
                        intXB = intersectionPointB->x;
                        intYB = intersectionPointB->y;
                        intSlotB = slot;

                        if (normalRadians != nullptr) {
                            intBN = normalRadians->y;
                        }
                    }
                }
            } else {
                intSlotA = slot;
                break;
            }
        }
    }

    if (intSlotA != nullptr && intersectionPointA != nullptr) {
        intersectionPointA->x = intXA;
        intersectionPointA->y = intYA;

        if (normalRadians != nullptr) {
            normalRadians->x = intAN;
        }
    }

    if (intSlotB != nullptr && intersectionPointB != nullptr) {
        intersectionPointB->x = intXB;
        intersectionPointB->y = intYB;

        if (normalRadians != nullptr) {
            normalRadians->y = intBN;
        }
    }

    return intSlotA;
}

Bone* Armature::getBone(const std::string& name) const {
    for (const auto& bone : _bones) {
        if (bone->getName() == name) {
            return bone;
        }
    }

    return nullptr;
}

Bone* Armature::getBoneByDisplay(void* display) const {
    const auto slot = getSlotByDisplay(display);

    return slot != nullptr ? slot->getParent() : nullptr;
}

Slot* Armature::getSlot(const std::string& name) const {
    for (const auto slot : _slots) {
        if (slot->getName() == name) {
            return slot;
        }
    }

    return nullptr;
}

Slot* Armature::getSlotByDisplay(void* display) const {
    if (display != nullptr) {
        for (const auto slot : _slots) {
            if (slot->getDisplay() == display) {
                return slot;
            }
        }
    }

    return nullptr;
}

void Armature::setCacheFrameRate(unsigned value) {
    if (_armatureData->cacheFrameRate != value) {
        _armatureData->cacheFrames(value);

        for (const auto& slot : _slots) {
            const auto childArmature = slot->getChildArmature();
            if (childArmature != nullptr && childArmature->getCacheFrameRate() == 0) {
                childArmature->setCacheFrameRate(value);
            }
        }
    }
}

void Armature::setClock(WorldClock* value) {
    if (_clock == value) {
        return;
    }

    if (_clock) {
        _clock->remove(this);
    }

    _clock = value;

    if (_clock) {
        _clock->add(this);
    }

    // Update childArmature clock.
    for (const auto& slot : _slots) {
        const auto childArmature = slot->getChildArmature();
        if (childArmature != nullptr) {
            childArmature->setClock(_clock);
        }
    }
}

void Armature::setReplacedTexture(void* value) {
    if (_replacedTexture == value) {
        return;
    }

    if (_replaceTextureAtlasData != nullptr) {
        _replaceTextureAtlasData->returnToPool();
        _replaceTextureAtlasData = nullptr;
    }

    _replacedTexture = value;

    for (const auto& slot : _slots) {
        slot->invalidUpdate();
        slot->update(-1);
    }
}

DRAGONBONES_NAMESPACE_END
