#include "Transition.h"
#include "GComponent.h"
#include "GRoot.h"
#include "tween/GPath.h"
#include "tween/GTween.h"
#include "utils/ByteBuffer.h"
#include "utils/ToolSet.h"

NS_FGUI_BEGIN
USING_NS_AX;
using namespace std;

const int OPTION_IGNORE_DISPLAY_CONTROLLER = 1;
const int OPTION_AUTO_STOP_DISABLED = 2;
const int OPTION_AUTO_STOP_AT_END = 4;

class TValueBase
{
};

class TValue_Visible : public TValueBase
{
public:
    bool visible;
};

class TValue_Animation : public TValueBase
{
public:
    int frame;
    bool playing;
    bool flag;
};

class TValue_Sound : public TValueBase
{
public:
    std::string sound;
    float volume;
};

class TValue_Transition : public TValueBase
{
public:
    std::string transName;
    int playTimes;
    Transition* trans;
    float stopTime;
};

class TValue_Shake : public TValueBase
{
public:
    float amplitude;
    float duration;
    ax::Vec2 lastOffset;
    ax::Vec2 offset;
};

class TValue_Text : public TValueBase
{
public:
    std::string text;
};

class TValue : public TValueBase
{
public:
    float f1;
    float f2;
    float f3;
    float f4;
    bool b1;
    bool b2;
    bool b3;

    TValue();
    ax::Vec2 getVec2() const;
    void setVec2(const ax::Vec2& value);
    ax::Vec4 getVec4() const;
    void setVec4(const ax::Vec4& value);
    ax::Color4B getColor() const;
    void setColor(const ax::Color4B& value);
};

TValue::TValue()
{
    f1 = f2 = f3 = f4 = 0;
    b1 = b2 = true;
    b3 = false;
}

ax::Vec2 TValue::getVec2() const
{
    return ax::Vec2(f1, f2);
}

void TValue::setVec2(const ax::Vec2& value)
{
    f1 = value.x;
    f2 = value.y;
}

ax::Vec4 TValue::getVec4() const
{
    return ax::Vec4(f1, f2, f3, f4);
}

void TValue::setVec4(const ax::Vec4& value)
{
    f1 = value.x;
    f2 = value.y;
    f3 = value.z;
    f4 = value.w;
}

ax::Color4B TValue::getColor() const
{
    return ax::Color4B(f1, f2, f3, f4);
}

void TValue::setColor(const ax::Color4B& value)
{
    f1 = value.r;
    f2 = value.g;
    f3 = value.b;
    f4 = value.a;
}

class TweenConfig
{
public:
    float duration;
    EaseType easeType;
    int repeat;
    bool yoyo;

    TValue* startValue;
    TValue* endValue;
    GPath* path;

    string endLabel;
    Transition::TransitionHook endHook;

    TweenConfig();
    ~TweenConfig();
};

TweenConfig::TweenConfig()
    : startValue(nullptr),
    endValue(nullptr),
    path(nullptr)
{
    easeType = EaseType::QuadOut;
    startValue = new TValue();
    endValue = new TValue();
}

TweenConfig::~TweenConfig()
{
    AX_SAFE_DELETE(path);
    AX_SAFE_DELETE(startValue);
    AX_SAFE_DELETE(endValue);
}

class TransitionItem
{
public:
    float time;
    string targetId;
    TransitionActionType type;
    TweenConfig* tweenConfig;
    string label;
    TValueBase* value;
    Transition::TransitionHook hook;

    //running properties
    GTweener* tweener;
    GObject* target;
    uint32_t displayLockToken;

    TransitionItem(TransitionActionType aType);
    ~TransitionItem();
};

TransitionItem::TransitionItem(TransitionActionType aType)
    : time(0),
    type(aType),
    hook(nullptr),
    tweenConfig(nullptr),
    tweener(nullptr),
    target(nullptr),
    displayLockToken(0)
{
    switch (type)
    {
    case TransitionActionType::XY:
    case TransitionActionType::Size:
    case TransitionActionType::Scale:
    case TransitionActionType::Pivot:
    case TransitionActionType::Skew:
    case TransitionActionType::Alpha:
    case TransitionActionType::Rotation:
    case TransitionActionType::Color:
    case TransitionActionType::ColorFilter:
        value = new TValue();
        break;

    case TransitionActionType::Animation:
        value = new TValue_Animation();
        break;

    case TransitionActionType::Shake:
        value = new TValue_Shake();
        break;

    case TransitionActionType::Sound:
        value = new TValue_Sound();
        break;

    case TransitionActionType::Transition:
        value = new TValue_Transition();
        break;

    case TransitionActionType::Visible:
        value = new TValue_Visible();
        break;

    case TransitionActionType::Text:
    case TransitionActionType::Icon:
        value = new TValue_Text();
        break;

    default:
        value = nullptr;
        break;
    }
}

TransitionItem::~TransitionItem()
{
    if (tweener != nullptr)
    {
        tweener->kill();
        tweener = nullptr;
    }

    target = nullptr;
    hook = nullptr;
    if (tweenConfig != nullptr)
    {
        tweenConfig->endHook = nullptr;
        delete tweenConfig;
    }

    AX_SAFE_DELETE(value);
}

Transition::Transition(GComponent* owner)
    : _owner(owner),
    _totalTimes(0),
    _totalTasks(0),
    _playing(false),
    _paused(false),
    _ownerBaseX(0),
    _ownerBaseY(0),
    _onComplete(nullptr),
    _options(0),
    _reversed(false),
    _totalDuration(0),
    _autoPlay(false),
    _autoPlayDelay(0),
    _timeScale(1),
    _startTime(0),
    _endTime(0)
{
}

Transition::~Transition()
{
    if (_playing)
        GTween::kill(this); //delay start

    for (auto& item : _items)
        delete item;

    _playing = false;
    _onComplete = nullptr;
}

void Transition::play(PlayCompleteCallback callback)
{
    play(1, 0, 0, -1, callback, false);
}

void Transition::play(int times, float delay, PlayCompleteCallback callback)
{
    play(times, delay, 0, -1, callback, false);
}

void Transition::play(int times, float delay, float startTime, float endTime, PlayCompleteCallback callback)
{
    play(times, delay, startTime, endTime, callback, false);
}

void Transition::playReverse(PlayCompleteCallback callback)
{
    play(1, 0, 0, -1, callback, true);
}

void Transition::playReverse(int times, float delay, PlayCompleteCallback callback)
{
    play(times, delay, 0, -1, callback, true);
}

void Transition::play(int times, float delay, float startTime, float endTime, PlayCompleteCallback onComplete, bool reverse)
{
    stop(true, true);

    _totalTimes = times;
    _reversed = reverse;
    _startTime = startTime;
    _endTime = endTime;
    _playing = true;
    _paused = false;
    _onComplete = onComplete;

    int cnt = (int)_items.size();
    for (int i = 0; i < cnt; i++)
    {
        TransitionItem* item = _items[i];
        if (item->target == nullptr)
        {
            if (!item->targetId.empty())
                item->target = _owner->getChildById(item->targetId);
            else
                item->target = _owner;
        }
        else if (item->target != _owner && item->target->getParent() != _owner) //maybe removed
            item->target = nullptr;

        if (item->target != nullptr && item->type == TransitionActionType::Transition)
        {
            TValue_Transition* value = (TValue_Transition*)item->value;
            Transition* trans = dynamic_cast<GComponent*>(item->target)->getTransition(value->transName);
            if (trans == this)
                trans = nullptr;
            if (trans != nullptr)
            {
                if (value->playTimes == 0) //stop
                {
                    int j;
                    for (j = i - 1; j >= 0; j--)
                    {
                        TransitionItem* item2 = _items[j];
                        if (item2->type == TransitionActionType::Transition)
                        {
                            TValue_Transition* value2 = (TValue_Transition*)item2->value;
                            if (value2->trans == trans)
                            {
                                value2->stopTime = item->time - item2->time;
                                break;
                            }
                        }
                    }
                    if (j < 0)
                        value->stopTime = 0;
                    else
                        trans = nullptr; //no need to handle stop anymore
                }
                else
                    value->stopTime = -1;
            }
            value->trans = trans;
        }
    }

    if (delay == 0)
        onDelayedPlay();
    else
        GTween::delayedCall(delay)->setTarget(this)->onComplete(AX_CALLBACK_0(Transition::onDelayedPlay, this));
}

void Transition::changePlayTimes(int value)
{
    _totalTimes = value;
}

void Transition::setAutoPlay(bool autoPlay, int times, float delay)
{
    if (_autoPlay != autoPlay)
    {
        _autoPlay = autoPlay;
        _autoPlayTimes = times;
        _autoPlayDelay = delay;
        if (_autoPlay)
        {
            if (_owner->onStage())
                play(_autoPlayTimes, _autoPlayDelay, nullptr);
        }
        else
        {
            if (!_owner->onStage())
                stop(false, true);
        }
    }
}

void Transition::stop()
{
    stop(true, false);
}

void Transition::stop(bool setToComplete, bool processCallback)
{
    if (!_playing)
        return;

    _playing = false;
    _totalTasks = 0;
    _totalTimes = 0;
    PlayCompleteCallback func = _onComplete;
    _onComplete = nullptr;

    int cnt = (int)_items.size();
    if (_reversed)
    {
        for (int i = cnt - 1; i >= 0; i--)
        {
            TransitionItem* item = _items[i];
            if (item->target == nullptr)
                continue;

            stopItem(item, setToComplete);
        }
    }
    else
    {
        for (int i = 0; i < cnt; i++)
        {
            TransitionItem* item = _items[i];
            if (item->target == nullptr)
                continue;

            stopItem(item, setToComplete);
        }
    }
    if (processCallback && func != nullptr)
        func();
}

void Transition::stopItem(TransitionItem* item, bool setToComplete)
{
    if (item->displayLockToken != 0)
    {
        item->target->releaseDisplayLock(item->displayLockToken);
        item->displayLockToken = 0;
    }

    if (item->tweener != nullptr)
    {
        item->tweener->kill(setToComplete);
        item->tweener = nullptr;

        if (item->type == TransitionActionType::Shake && !setToComplete)
        {
            item->target->_gearLocked = true;
            item->target->setPosition(item->target->getX() - ((TValue_Shake*)item->value)->lastOffset.x, item->target->getY() - ((TValue_Shake*)item->value)->lastOffset.y);
            item->target->_gearLocked = false;
        }
    }
}

void Transition::setPaused(bool paused)
{
    if (!_playing || _paused == paused)
        return;

    _paused = paused;
    GTweener* tweener = GTween::getTween(this);
    if (tweener != nullptr)
        tweener->setPaused(paused);

    for (auto& item : _items)
    {
        if (item->target == nullptr)
            continue;

        if (item->type == TransitionActionType::Transition)
        {
            if (((TValue_Transition*)item->value)->trans != nullptr)
                ((TValue_Transition*)item->value)->trans->setPaused(paused);
        }
        else if (item->type == TransitionActionType::Animation)
        {
            if (paused)
            {
                ((TValue_Animation*)item->value)->flag = item->target->getProp(ObjectPropID::Playing).asBool();
                item->target->setProp(ObjectPropID::Playing, Value(false));
            }
            else
                item->target->setProp(ObjectPropID::Playing, Value(((TValue_Animation*)item->value)->flag));
        }

        if (item->tweener != nullptr)
            item->tweener->setPaused(paused);
    }
}

void Transition::setValue(const std::string& label, const ValueVector& values)
{
    for (auto& item : _items)
    {
        void* value;
        if (item->label == label)
        {
            if (item->tweenConfig != nullptr)
                value = item->tweenConfig->startValue;
            else
                value = item->value;
        }
        else if (item->tweenConfig != nullptr && item->tweenConfig->endLabel == label)
        {
            value = item->tweenConfig->endValue;
        }
        else
            continue;

        switch (item->type)
        {
        case TransitionActionType::XY:
        case TransitionActionType::Size:
        case TransitionActionType::Pivot:
        case TransitionActionType::Scale:
        case TransitionActionType::Skew:
        {
            TValue* tvalue = (TValue*)value;
            tvalue->b1 = true;
            tvalue->b2 = true;
            tvalue->f1 = values[0].asFloat();
            tvalue->f2 = values[1].asFloat();
            break;
        }

        case TransitionActionType::Alpha:
        case TransitionActionType::Rotation:
            ((TValue*)value)->f1 = values[0].asFloat();
            ;
            break;

        case TransitionActionType::Color:
        {
            uint32_t v = values[0].asUnsignedInt();
            ((TValue*)value)->setColor(Color4B((v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF, (v >> 24) & 0xFF));
            break;
        }

        case TransitionActionType::Animation:
        {
            TValue_Animation* tvalue = (TValue_Animation*)value;
            tvalue->frame = values[0].asInt();
            if (values.size() > 1)
                tvalue->playing = values[1].asBool();
            break;
        }

        case TransitionActionType::Visible:
            ((TValue_Visible*)value)->visible = values[0].asBool();
            break;

        case TransitionActionType::Sound:
        {
            TValue_Sound* tvalue = (TValue_Sound*)value;
            tvalue->sound = values[0].asString();
            if (values.size() > 1)
                tvalue->volume = values[1].asFloat();
            break;
        }

        case TransitionActionType::Transition:
        {
            TValue_Transition* tvalue = (TValue_Transition*)value;
            tvalue->transName = values[0].asString();
            if (values.size() > 1)
                tvalue->playTimes = values[1].asInt();
            break;
        }

        case TransitionActionType::Shake:
        {
            ((TValue_Shake*)value)->amplitude = values[0].asFloat();
            if (values.size() > 1)
                ((TValue_Shake*)value)->duration = values[1].asFloat();
            break;
        }

        case TransitionActionType::ColorFilter:
        {
            TValue* tvalue = (TValue*)value;
            tvalue->f1 = values[0].asFloat();
            tvalue->f2 = values[1].asFloat();
            tvalue->f3 = values[2].asFloat();
            tvalue->f4 = values[3].asFloat();
            break;
        }

        case TransitionActionType::Text:
        case TransitionActionType::Icon:
            ((TValue_Text*)value)->text = values[0].asString();
            break;
        default:
            break;
        }
    }
}

void Transition::setHook(const std::string& label, TransitionHook callback)
{
    for (auto& item : _items)
    {
        if (item->label == label)
        {
            item->hook = callback;
            break;
        }
        else if (item->tweenConfig != nullptr && item->tweenConfig->endLabel == label)
        {
            item->tweenConfig->endHook = callback;
            break;
        }
    }
}

void Transition::clearHooks()
{
    for (auto& item : _items)
    {
        item->hook = nullptr;
        if (item->tweenConfig != nullptr)
            item->tweenConfig->endHook = nullptr;
    }
}

void Transition::setTarget(const std::string& label, GObject* newTarget)
{
    for (auto& item : _items)
    {
        if (item->label == label)
        {

            item->targetId = newTarget->id;
            item->target = nullptr;
        }
    }
}

void Transition::setDuration(const std::string& label, float value)
{
    for (auto& item : _items)
    {
        if (item->tweenConfig != nullptr && item->label == label)
            item->tweenConfig->duration = value;
    }
}

float Transition::getLabelTime(const string& label) const
{
    for (auto& item : _items)
    {
        if (item->label == label)
            return item->time;
        else if (item->tweenConfig != nullptr && item->label == label)
            return item->time + item->tweenConfig->duration;
    }

    return NAN;
}

void Transition::setTimeScale(float value)
{
    if (_timeScale != value)
    {
        _timeScale = value;

        for (auto& item : _items)
        {
            if (item->tweener != nullptr)
                item->tweener->setTimeScale(value);
            else if (item->type == TransitionActionType::Transition)
            {
                if (((TValue_Transition*)item->value)->trans != nullptr)
                    ((TValue_Transition*)item->value)->trans->setTimeScale(value);
            }
            else if (item->type == TransitionActionType::Animation)
            {
                if (item->target != nullptr)
                    item->target->setProp(ObjectPropID::TimeScale, Value(value));
            }
        }
    }
}

void Transition::updateFromRelations(const std::string& targetId, float dx, float dy)
{
    int cnt = (int)_items.size();
    if (cnt == 0)
        return;

    for (auto& item : _items)
    {
        if (item->type == TransitionActionType::XY && item->targetId == targetId)
        {
            if (item->tweenConfig != nullptr)
            {
                if(!item->tweenConfig->startValue->b3){
                    item->tweenConfig->startValue->f1 += dx;
                    item->tweenConfig->startValue->f2 += dy;
                    item->tweenConfig->endValue->f1 += dx;
                    item->tweenConfig->endValue->f2 += dy;
                }
            }
            else
            {
                if(!((TValue*)item->value)->b3){
                    ((TValue*)item->value)->f1 += dx;
                    ((TValue*)item->value)->f2 += dy;
                }
            }
        }
    }
}

void Transition::onOwnerAddedToStage()
{
    if (_autoPlay && !_playing)
        play(_autoPlayTimes, _autoPlayDelay, nullptr);
}

void Transition::onOwnerRemovedFromStage()
{
    if ((_options & OPTION_AUTO_STOP_DISABLED) == 0)
        stop((_options & OPTION_AUTO_STOP_AT_END) != 0 ? true : false, false);
}

void Transition::onDelayedPlay()
{
    internalPlay();

    _playing = _totalTasks > 0;
    if (_playing)
    {
        if ((_options & OPTION_IGNORE_DISPLAY_CONTROLLER) != 0)
        {
            for (auto& item : _items)
            {
                if (item->target != nullptr && item->target != _owner)
                    item->displayLockToken = item->target->addDisplayLock();
            }
        }
    }
    else if (_onComplete != nullptr)
    {
        PlayCompleteCallback func = _onComplete;
        _onComplete = nullptr;
        func();
    }
}

void Transition::internalPlay()
{
    _ownerBaseX = _owner->getX();
    _ownerBaseY = _owner->getY();

    _totalTasks = 0;

    bool needSkipAnimations = false;
    int cnt = (int)_items.size();
    if (!_reversed)
    {
        for (int i = 0; i < cnt; i++)
        {
            TransitionItem* item = _items[i];
            if (item->target == nullptr)
                continue;

            if (item->type == TransitionActionType::Animation && _startTime != 0 && item->time <= _startTime)
            {
                needSkipAnimations = true;
                ((TValue_Animation*)item->value)->flag = false;
            }
            else
                playItem(item);
        }
    }
    else
    {
        for (int i = cnt - 1; i >= 0; i--)
        {
            TransitionItem* item = _items[i];
            if (item->target == nullptr)
                continue;

            playItem(item);
        }
    }

    if (needSkipAnimations)
        skipAnimations();
}

void Transition::playItem(TransitionItem* item)
{
    float time;
    if (item->tweenConfig != nullptr)
    {
        if (_reversed)
            time = (_totalDuration - item->time - item->tweenConfig->duration);
        else
            time = item->time;

        if (_endTime == -1 || time <= _endTime)
        {
            TValue* startValue;
            TValue* endValue;

            if (_reversed)
            {
                startValue = item->tweenConfig->endValue;
                endValue = item->tweenConfig->startValue;
            }
            else
            {
                startValue = item->tweenConfig->startValue;
                endValue = item->tweenConfig->endValue;
            }

            ((TValue*)item->value)->b1 = startValue->b1 || endValue->b1;
            ((TValue*)item->value)->b2 = startValue->b2 || endValue->b2;

            switch (item->type)
            {
            case TransitionActionType::XY:
            case TransitionActionType::Size:
            case TransitionActionType::Scale:
            case TransitionActionType::Skew:
                item->tweener = GTween::to(startValue->getVec2(), endValue->getVec2(), item->tweenConfig->duration);
                break;

            case TransitionActionType::Alpha:
            case TransitionActionType::Rotation:
                item->tweener = GTween::to(startValue->f1, endValue->f1, item->tweenConfig->duration);
                break;

            case TransitionActionType::Color:
                item->tweener = GTween::to(startValue->getColor(), endValue->getColor(), item->tweenConfig->duration);
                break;

            case TransitionActionType::ColorFilter:
                item->tweener = GTween::to(startValue->getVec4(), endValue->getVec4(), item->tweenConfig->duration);
                break;
            default:
                break;
            }

            item->tweener->setDelay(time)
                ->setEase(item->tweenConfig->easeType)
                ->setRepeat(item->tweenConfig->repeat, item->tweenConfig->yoyo)
                ->setTimeScale(_timeScale)
                ->setTargetAny(item)
                ->onStart(AX_CALLBACK_1(Transition::onTweenStart, this))
                ->onUpdate(AX_CALLBACK_1(Transition::onTweenUpdate, this))
                ->onComplete1(AX_CALLBACK_1(Transition::onTweenComplete, this));

            if (_endTime >= 0)
                item->tweener->setBreakpoint(_endTime - time);

            _totalTasks++;
        }
    }
    else if (item->type == TransitionActionType::Shake)
    {
        TValue_Shake* value = (TValue_Shake*)item->value;

        if (_reversed)
            time = (_totalDuration - item->time - value->duration);
        else
            time = item->time;

        if (_endTime == -1 || time <= _endTime)
        {
            value->lastOffset.setZero();
            value->offset.setZero();
            item->tweener = GTween::shake(Vec2::ZERO, value->amplitude, value->duration)
                ->setDelay(time)
                ->setTimeScale(_timeScale)
                ->setTargetAny(item)
                ->onStart(AX_CALLBACK_1(Transition::onTweenStart, this))
                ->onUpdate(AX_CALLBACK_1(Transition::onTweenUpdate, this))
                ->onComplete1(AX_CALLBACK_1(Transition::onTweenComplete, this));

            if (_endTime >= 0)
                item->tweener->setBreakpoint(_endTime - item->time);

            _totalTasks++;
        }
    }
    else
    {
        if (_reversed)
            time = (_totalDuration - item->time);
        else
            time = item->time;

        if (time <= _startTime)
        {
            applyValue(item);
            callHook(item, false);
        }
        else if (_endTime == -1 || time <= _endTime)
        {
            _totalTasks++;
            item->tweener = GTween::delayedCall(time)
                ->setTimeScale(_timeScale)
                ->setTargetAny(item)
                ->onComplete1(AX_CALLBACK_1(Transition::onDelayedPlayItem, this));
        }
    }

    if (item->tweener != nullptr)
        item->tweener->seek(_startTime);
}

void Transition::skipAnimations()
{
    int frame;
    float playStartTime;
    float playTotalTime;
    GObject* target;
    TValue_Animation* value;
    TransitionItem* item;

    int cnt = (int)_items.size();
    for (int i = 0; i < cnt; i++)
    {
        item = _items[i];
        if (item->type != TransitionActionType::Animation || item->time > _startTime)
            continue;

        value = (TValue_Animation*)item->value;
        if (value->flag)
            continue;

        target = item->target;
        frame = target->getProp(ObjectPropID::Frame).asInt();
        playStartTime = target->getProp(ObjectPropID::Playing).asBool() ? 0 : -1;
        playTotalTime = 0;

        for (int j = i; j < cnt; j++)
        {
            item = _items[j];
            if (item->type != TransitionActionType::Animation || item->target != target || item->time > _startTime)
                continue;

            value = (TValue_Animation*)item->value;
            value->flag = true;

            if (value->frame != -1)
            {
                frame = value->frame;
                if (value->playing)
                    playStartTime = item->time;
                else
                    playStartTime = -1;
                playTotalTime = 0;
            }
            else
            {
                if (value->playing)
                {
                    if (playStartTime < 0)
                        playStartTime = item->time;
                }
                else
                {
                    if (playStartTime >= 0)
                        playTotalTime += (item->time - playStartTime);
                    playStartTime = -1;
                }
            }

            callHook(item, false);
        }

        if (playStartTime >= 0)
            playTotalTime += (_startTime - playStartTime);

        target->setProp(ObjectPropID::Playing, Value(playStartTime >= 0));
        target->setProp(ObjectPropID::Frame, Value(frame));
        if (playTotalTime > 0)
            target->setProp(ObjectPropID::DeltaTime, Value(playTotalTime));
    }
}

void Transition::onDelayedPlayItem(GTweener* tweener)
{
    TransitionItem* item = (TransitionItem*)tweener->getTarget();
    item->tweener = nullptr;
    _totalTasks--;

    applyValue(item);
    callHook(item, false);

    checkAllComplete();
}

void Transition::onTweenStart(GTweener* tweener)
{
    TransitionItem* item = (TransitionItem*)tweener->getTarget();

    if (item->type == TransitionActionType::XY || item->type == TransitionActionType::Size)
    {
        TValue* startValue;
        TValue* endValue;

        if (_reversed)
        {
            startValue = item->tweenConfig->endValue;
            endValue = item->tweenConfig->startValue;
        }
        else
        {
            startValue = item->tweenConfig->startValue;
            endValue = item->tweenConfig->endValue;
        }

        if (item->type == TransitionActionType::XY)
        {
            if (item->target != _owner)
            {
                if (!startValue->b1)
                    tweener->startValue.x = item->target->getX();
                else if (startValue->b3) //percent
                    tweener->startValue.x = startValue->f1 * _owner->getWidth();

                if (!startValue->b2)
                    tweener->startValue.y = item->target->getY();
                else if (startValue->b3) //percent
                    tweener->startValue.y = startValue->f2 * _owner->getHeight();

                if (!endValue->b1)
                    tweener->endValue.x = tweener->startValue.x;
                else if (endValue->b3)
                    tweener->endValue.x = endValue->f1 * _owner->getWidth();

                if (!endValue->b2)
                    tweener->endValue.y = tweener->startValue.y;
                else if (endValue->b3)
                    tweener->endValue.y = endValue->f2 * _owner->getHeight();
            }
            else
            {
                if (!startValue->b1)
                    tweener->startValue.x = item->target->getX() - _ownerBaseX;
                if (!startValue->b2)
                    tweener->startValue.y = item->target->getY() - _ownerBaseY;

                if (!endValue->b1)
                    tweener->endValue.x = tweener->startValue.x;
                if (!endValue->b2)
                    tweener->endValue.y = tweener->startValue.y;
            }
        }
        else
        {
            if (!startValue->b1)
                tweener->startValue.x = item->target->getWidth();
            if (!startValue->b2)
                tweener->startValue.y = item->target->getHeight();

            if (!endValue->b1)
                tweener->endValue.x = tweener->startValue.x;
            if (!endValue->b2)
                tweener->endValue.y = tweener->startValue.y;
        }

        if (item->tweenConfig->path != nullptr)
        {
            ((TValue*)item->value)->b1 = ((TValue*)item->value)->b2 = true;
            tweener->setPath(item->tweenConfig->path);
        }
    }

    callHook(item, false);
}

void Transition::onTweenUpdate(GTweener* tweener)
{
    TransitionItem* item = (TransitionItem*)tweener->getTarget();
    switch (item->type)
    {
    case TransitionActionType::XY:
    case TransitionActionType::Size:
    case TransitionActionType::Scale:
    case TransitionActionType::Skew:
        if (item->tweenConfig->path != nullptr)
            ((TValue*)item->value)->setVec2(tweener->value.getVec2() + tweener->startValue.getVec2());
        else
            ((TValue*)item->value)->setVec2(tweener->value.getVec2());
        break;

    case TransitionActionType::Alpha:
    case TransitionActionType::Rotation:
        ((TValue*)item->value)->f1 = tweener->value.x;
        break;

    case TransitionActionType::Color:
        ((TValue*)item->value)->setColor(tweener->value.getColor());
        break;

    case TransitionActionType::ColorFilter:
        ((TValue*)item->value)->setVec4(tweener->value.getVec4());
        break;

    case TransitionActionType::Shake:
        ((TValue_Shake*)item->value)->offset = tweener->deltaValue.getVec2();
        break;
    default:
        break;
    }
    applyValue(item);
}

void Transition::onTweenComplete(GTweener* tweener)
{
    TransitionItem* item = (TransitionItem*)tweener->getTarget();
    item->tweener = nullptr;
    _totalTasks--;

    if (tweener->allCompleted())
        callHook(item, true);

    checkAllComplete();
}

void Transition::onPlayTransCompleted(TransitionItem* item)
{
    _totalTasks--;

    checkAllComplete();
}

void Transition::callHook(TransitionItem* item, bool tweenEnd)
{
    if (tweenEnd)
    {
        if (item->tweenConfig != nullptr && item->tweenConfig->endHook != nullptr)
            item->tweenConfig->endHook();
    }
    else
    {
        if (item->time >= _startTime && item->hook != nullptr)
            item->hook();
    }
}

void Transition::checkAllComplete()
{
    if (_playing && _totalTasks == 0)
    {
        if (_totalTimes < 0)
        {
            internalPlay();
        }
        else
        {
            _totalTimes--;
            if (_totalTimes > 0)
                internalPlay();
            else
            {
                _playing = false;

                for (auto& item : _items)
                {
                    if (item->target != nullptr && item->displayLockToken != 0)
                    {
                        item->target->releaseDisplayLock(item->displayLockToken);
                        item->displayLockToken = 0;
                    }
                }

                if (_onComplete != nullptr)
                {
                    PlayCompleteCallback func = _onComplete;
                    _onComplete = nullptr;
                    func();
                }
            }
        }
    }
}

void Transition::applyValue(TransitionItem* item)
{
    item->target->_gearLocked = true;

    switch (item->type)
    {
    case TransitionActionType::XY:
    {
        TValue* value = (TValue*)item->value;
        if (item->target == _owner)
        {
            if (value->b1 && value->b2)
                item->target->setPosition(value->f1 + _ownerBaseX, value->f2 + _ownerBaseY);
            else if (value->b1)
                item->target->setX(value->f1 + _ownerBaseX);
            else
                item->target->setY(value->f2 + _ownerBaseY);
        }
        else
        {
            if (value->b3) //position in percent
            {
                if (value->b1 && value->b2)
                    item->target->setPosition(value->f1 * _owner->getWidth(), value->f2 * _owner->getHeight());
                else if (value->b1)
                    item->target->setX(value->f1 * _owner->getWidth());
                else if (value->b2)
                    item->target->setY(value->f2 * _owner->getHeight());
            }
            else
            {
                if (value->b1 && value->b2)
                    item->target->setPosition(value->f1, value->f2);
                else if (value->b1)
                    item->target->setX(value->f1);
                else if (value->b2)
                    item->target->setY(value->f2);
            }
        }
    }
    break;

    case TransitionActionType::Size:
    {
        TValue* value = (TValue*)item->value;
        if (!value->b1)
            value->f1 = item->target->getWidth();
        if (!value->b2)
            value->f2 = item->target->getHeight();
        item->target->setSize(value->f1, value->f2);
    }
    break;

    case TransitionActionType::Pivot:
        item->target->setPivot(((TValue*)item->value)->f1, ((TValue*)item->value)->f2, item->target->isPivotAsAnchor());
        break;

    case TransitionActionType::Alpha:
        item->target->setAlpha(((TValue*)item->value)->f1);
        break;

    case TransitionActionType::Rotation:
        item->target->setRotation(((TValue*)item->value)->f1);
        break;

    case TransitionActionType::Scale:
        item->target->setScale(((TValue*)item->value)->f1, ((TValue*)item->value)->f2);
        break;

    case TransitionActionType::Skew:
        item->target->setSkewX(((TValue*)item->value)->f1);
        item->target->setSkewY(((TValue*)item->value)->f2);
        break;

    case TransitionActionType::Color:
        item->target->setProp(ObjectPropID::Color, Value(ToolSet::colorToInt((Color3B)((TValue*)item->value)->getColor())));
        break;

    case TransitionActionType::Animation:
    {
        TValue_Animation* value = (TValue_Animation*)item->value;
        if (value->frame >= 0)
            item->target->setProp(ObjectPropID::Frame, Value(value->frame));
        item->target->setProp(ObjectPropID::Playing, Value(value->playing));
        item->target->setProp(ObjectPropID::TimeScale, Value(_timeScale));
        break;
    }

    case TransitionActionType::Visible:
        item->target->setVisible(((TValue_Visible*)item->value)->visible);
        break;

    case TransitionActionType::Shake:
    {
        TValue_Shake* value = (TValue_Shake*)item->value;
        item->target->setPosition(item->target->getX() - value->lastOffset.x + value->offset.x, item->target->getY() - value->lastOffset.y + value->offset.y);
        value->lastOffset = value->offset;
        break;
    }

    case TransitionActionType::Transition:
        if (_playing)
        {
            TValue_Transition* value = (TValue_Transition*)item->value;
            if (value->trans != nullptr)
            {
                _totalTasks++;

                float startTime = _startTime > item->time ? (_startTime - item->time) : 0;
                float endTime = _endTime >= 0 ? (_endTime - item->time) : -1;
                if (value->stopTime >= 0 && (endTime < 0 || endTime > value->stopTime))
                    endTime = value->stopTime;
                value->trans->setTimeScale(_timeScale);
                value->trans->play(value->playTimes, 0, startTime, endTime, [this, item]() { onPlayTransCompleted(item); }, _reversed);
            }
        }
        break;

    case TransitionActionType::Sound:
        if (_playing && item->time >= _startTime)
        {
            TValue_Sound* value = (TValue_Sound*)item->value;
            if (!value->sound.empty())
                UIRoot->playSound(value->sound, value->volume);
            break;
        }

    case TransitionActionType::ColorFilter:
        break;

    case TransitionActionType::Text:
        item->target->setText(((TValue_Text*)item->value)->text);
        break;

    case TransitionActionType::Icon:
        item->target->setIcon(((TValue_Text*)item->value)->text);
        break;
    default:
        break;
    }

    item->target->_gearLocked = false;
}

static std::vector<GPathPoint> helperPoints;

void Transition::setup(ByteBuffer* buffer)
{
    name = buffer->readS();
    _options = buffer->readInt();
    _autoPlay = buffer->readBool();
    _autoPlayTimes = buffer->readInt();
    _autoPlayDelay = buffer->readFloat();

    int cnt = buffer->readShort();
    for (int i = 0; i < cnt; i++)
    {
        int dataLen = buffer->readUshort();
        int curPos = buffer->getPos();

        buffer->seek(curPos, 0);

        TransitionItem* item = new TransitionItem((TransitionActionType)buffer->readByte());
        _items.push_back(item);

        item->time = buffer->readFloat();
        int targetId = buffer->readShort();
        if (targetId < 0)
            item->targetId = STD_STRING_EMPTY;
        else
            item->targetId = _owner->getChildAt(targetId)->id;
        item->label = buffer->readS();

        if (buffer->readBool())
        {
            buffer->seek(curPos, 1);

            item->tweenConfig = new TweenConfig();
            item->tweenConfig->duration = buffer->readFloat();
            if (item->time + item->tweenConfig->duration > _totalDuration)
                _totalDuration = item->time + item->tweenConfig->duration;
            item->tweenConfig->easeType = (EaseType)buffer->readByte();
            item->tweenConfig->repeat = buffer->readInt();
            item->tweenConfig->yoyo = buffer->readBool();
            item->tweenConfig->endLabel = buffer->readS();

            buffer->seek(curPos, 2);

            decodeValue(item, buffer, item->tweenConfig->startValue);

            buffer->seek(curPos, 3);

            decodeValue(item, buffer, item->tweenConfig->endValue);

            if (buffer->version >= 2)
            {
                int pathLen = buffer->readInt();
                if (pathLen > 0)
                {
                    item->tweenConfig->path = new GPath();
                    std::vector<GPathPoint>& pts = helperPoints;
                    pts.clear();

                    Vec3 v0, v1, v2;

                    for (int j = 0; j < pathLen; j++)
                    {
                        GPathPoint::CurveType curveType = (GPathPoint::CurveType)buffer->readByte();
                        switch (curveType)
                        {
                        case GPathPoint::CurveType::Bezier:
                            v0.x = buffer->readFloat();
                            v0.y = buffer->readFloat();
                            v1.x = buffer->readFloat();
                            v1.y = buffer->readFloat();
                            pts.push_back(GPathPoint(v0, v1));
                            break;

                        case GPathPoint::CurveType::CubicBezier:
                            v0.x = buffer->readFloat();
                            v0.y = buffer->readFloat();
                            v1.x = buffer->readFloat();
                            v1.y = buffer->readFloat();
                            v2.x = buffer->readFloat();
                            v2.y = buffer->readFloat();
                            pts.push_back(GPathPoint(v0, v1, v2));
                            break;

                        default:
                            v0.x = buffer->readFloat();
                            v0.y = buffer->readFloat();
                            pts.push_back(GPathPoint(v0, curveType));
                            break;
                        }
                    }

                    item->tweenConfig->path->create(pts.data(), (int)pts.size());
                }
            }
        }
        else
        {
            if (item->time > _totalDuration)
                _totalDuration = item->time;

            buffer->seek(curPos, 2);

            decodeValue(item, buffer, item->value);
        }

        buffer->setPos(curPos + dataLen);
    }
}

void Transition::decodeValue(TransitionItem* item, ByteBuffer* buffer, void* value)
{
    switch (item->type)
    {
    case TransitionActionType::XY:
    case TransitionActionType::Size:
    case TransitionActionType::Pivot:
    case TransitionActionType::Skew:
    {
        TValue* tvalue = (TValue*)value;
        tvalue->b1 = buffer->readBool();
        tvalue->b2 = buffer->readBool();
        tvalue->f1 = buffer->readFloat();
        tvalue->f2 = buffer->readFloat();

        if (buffer->version >= 2 && item->type == TransitionActionType::XY)
            tvalue->b3 = buffer->readBool(); //percent
        break;
    }

    case TransitionActionType::Alpha:
    case TransitionActionType::Rotation:
        ((TValue*)value)->f1 = buffer->readFloat();
        break;

    case TransitionActionType::Scale:
    {
        ((TValue*)value)->f1 = buffer->readFloat();
        ((TValue*)value)->f2 = buffer->readFloat();
        break;
    }

    case TransitionActionType::Color:
        ((TValue*)value)->setColor(buffer->readColor());
        break;

    case TransitionActionType::Animation:
    {
        ((TValue_Animation*)value)->playing = buffer->readBool();
        ((TValue_Animation*)value)->frame = buffer->readInt();
        break;
    }

    case TransitionActionType::Visible:
        ((TValue_Visible*)value)->visible = buffer->readBool();
        break;

    case TransitionActionType::Sound:
    {
        ((TValue_Sound*)value)->sound = buffer->readS();
        ((TValue_Sound*)value)->volume = buffer->readFloat();
        break;
    }

    case TransitionActionType::Transition:
    {
        ((TValue_Transition*)value)->transName = buffer->readS();
        ((TValue_Transition*)value)->playTimes = buffer->readInt();
        break;
    }

    case TransitionActionType::Shake:
    {
        ((TValue_Shake*)value)->amplitude = buffer->readFloat();
        ((TValue_Shake*)value)->duration = buffer->readFloat();
        break;
    }

    case TransitionActionType::ColorFilter:
    {
        TValue* tvalue = (TValue*)value;
        tvalue->f1 = buffer->readFloat();
        tvalue->f2 = buffer->readFloat();
        tvalue->f3 = buffer->readFloat();
        tvalue->f4 = buffer->readFloat();
        break;
    }

    case TransitionActionType::Text:
    case TransitionActionType::Icon:
        ((TValue_Text*)value)->text = buffer->readS();
        break;

    default:
        break;
    }
}

NS_FGUI_END