/*
 * Copyright 2006 The Android Open Source Project
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */


#include "SkAnimateMaker.h"
#include "SkAnimator.h"
#include "SkAnimatorScript.h"
#include "SkDisplayable.h"
#include "SkDisplayApply.h"
#include "SkDisplayList.h"
#include "SkDisplayMovie.h"
#include "SkDisplayType.h"
#include "SkExtras.h"
#include "SkMemberInfo.h"
#include "SkStream.h"
#include "SkSystemEventTypes.h"
#include "SkTime.h"

class DefaultTimeline : public SkAnimator::Timeline {
    virtual SkMSec getMSecs() const {
        return SkTime::GetMSecs();
    }
} gDefaultTimeline;

SkAnimateMaker::SkAnimateMaker(SkAnimator* animator, SkCanvas* canvas, SkPaint* paint)
    : fActiveEvent(nullptr), fAdjustedStart(0), fCanvas(canvas), fEnableTime(0),
        fHostEventSinkID(0), fMinimumInterval((SkMSec) -1), fPaint(paint), fParentMaker(nullptr),
        fTimeline(&gDefaultTimeline), fInInclude(false), fInMovie(false),
        fFirstScriptError(false), fLoaded(false), fIDs(256), fAnimator(animator)
{
    fScreenplay.time = 0;
#if defined SK_DEBUG && defined SK_DEBUG_ANIMATION_TIMING
    fDebugTimeBase = (SkMSec) -1;
#endif
#ifdef SK_DUMP_ENABLED
    fDumpEvents = fDumpGConditions = fDumpPosts = false;
#endif
}

SkAnimateMaker::~SkAnimateMaker() {
    deleteMembers();
}

#if 0
SkMSec SkAnimateMaker::adjustDelay(SkMSec expectedBase, SkMSec delay) {
    SkMSec appTime = (*fTimeCallBack)();
    if (appTime)
        delay -= appTime - expectedBase;
    if (delay < 0)
        delay = 0;
    return delay;
}
#endif

void SkAnimateMaker::appendActive(SkActive* active) {
    fDisplayList.append(active);
}

void SkAnimateMaker::clearExtraPropertyCallBack(SkDisplayTypes type) {
    SkExtras** end = fExtras.end();
    for (SkExtras** extraPtr = fExtras.begin(); extraPtr < end; extraPtr++) {
        SkExtras* extra = *extraPtr;
        if (extra->definesType(type)) {
            extra->fExtraCallBack = nullptr;
            extra->fExtraStorage = nullptr;
            break;
        }
    }
}

bool SkAnimateMaker::computeID(SkDisplayable* displayable, SkDisplayable* parent, SkString* newID) {
    const char* script;
  if (findKey(displayable, &script) == false)
        return true;
    return SkAnimatorScript::EvaluateString(*this, displayable, parent, script, newID);
}

SkDisplayable* SkAnimateMaker::createInstance(const char name[], size_t len) {
    SkDisplayTypes type = SkDisplayType::GetType(this, name, len );
    if ((int)type >= 0)
        return SkDisplayType::CreateInstance(this, type);
    return nullptr;
}

// differs from SkAnimator::decodeStream in that it does not reset error state
bool SkAnimateMaker::decodeStream(SkStream* stream)
{
    SkDisplayXMLParser parser(*this);
    return parser.parse(*stream);
}

// differs from SkAnimator::decodeURI in that it does not set URI base
bool SkAnimateMaker::decodeURI(const char uri[]) {
//  SkDebugf("animator decode %s\n", uri);

//    SkStream* stream = SkStream::GetURIStream(fPrefix.c_str(), uri);
    SkAutoTDelete<SkStream> stream(SkStream::NewFromFile(uri));
    if (stream.get()) {
        bool success = decodeStream(stream);
        if (hasError() && fError.hasNoun() == false)
            fError.setNoun(uri);
        return success;
    } else {
        return false;
    }
}

#if defined SK_DEBUG && 0
//used for the if'd out section of deleteMembers
#include "SkTSearch.h"

extern "C" {
    int compare_disp(const void* a, const void* b) {
        return *(const SkDisplayable**)a - *(const SkDisplayable**)b;
    }
}
#endif

void SkAnimateMaker::delayEnable(SkApply* apply, SkMSec time) {
    int index = fDelayed.find(apply);
    if (index < 0) {
        *fDelayed.append() = apply;
    }

    (new SkEvent(SK_EventType_Delay, fAnimator->getSinkID()))->postTime(time);
}

void SkAnimateMaker::deleteMembers() {
    int index;
#if defined SK_DEBUG && 0
    //this code checks to see if helpers are among the children, but it is not complete -
    //it should check the children of the children
    int result;
    SkTDArray<SkDisplayable*> children(fChildren.begin(), fChildren.count());
    SkQSort(children.begin(), children.count(), sizeof(SkDisplayable*),compare_disp);
    for (index = 0; index < fHelpers.count(); index++) {
        SkDisplayable* helper = fHelpers[index];
        result = SkTSearch(children.begin(), children.count(), helper, sizeof(SkDisplayable*));
        SkASSERT(result < 0);
    }
#endif
    for (index = 0; index < fChildren.count(); index++) {
        SkDisplayable* child = fChildren[index];
        delete child;
    }
    for (index = 0; index < fHelpers.count(); index++) {
        SkDisplayable* helper = fHelpers[index];
        delete helper;
    }
    for (index = 0; index < fExtras.count(); index++) {
        SkExtras* extras = fExtras[index];
        delete extras;
    }
}

void SkAnimateMaker::doDelayedEvent() {
    fEnableTime = getAppTime();
    for (int index = 0; index < fDelayed.count(); ) {
        SkDisplayable* child = fDelayed[index];
        SkASSERT(child->isApply());
        SkApply* apply = (SkApply*) child;
        apply->interpolate(*this, fEnableTime);
        if (apply->hasDelayedAnimator())
            index++;
        else
            fDelayed.remove(index);
    }
}

bool SkAnimateMaker::doEvent(const SkEvent& event) {
    return (!fInMovie || fLoaded) && fAnimator->doEvent(event);
}

#ifdef SK_DUMP_ENABLED
void SkAnimateMaker::dump(const char* match) {
        SkTDict<SkDisplayable*>::Iter iter(fIDs);
        const char* name;
        SkDisplayable* result;
        while ((name = iter.next(&result)) != nullptr) {
            if (strcmp(match,name) == 0)
                result->dump(this);
        }
}
#endif

int SkAnimateMaker::dynamicProperty(SkString& nameStr, SkDisplayable** displayablePtr ) {
    const char* name = nameStr.c_str();
    const char* dot = strchr(name, '.');
    SkASSERT(dot);
    SkDisplayable* displayable;
    if (find(name, dot - name, &displayable) == false) {
        SkASSERT(0);
        return 0;
    }
    const char* fieldName = dot + 1;
    const SkMemberInfo* memberInfo = displayable->getMember(fieldName);
    *displayablePtr = displayable;
    return (int) memberInfo->fOffset;
}

SkMSec SkAnimateMaker::getAppTime() const {
    return fTimeline->getMSecs();
}

#ifdef SK_DEBUG
SkAnimator* SkAnimateMaker::getRoot()
{
    SkAnimateMaker* maker = this;
    while (maker->fParentMaker)
        maker = maker->fParentMaker;
    return maker == this ? nullptr : maker->fAnimator;
}
#endif

void SkAnimateMaker::helperAdd(SkDisplayable* trackMe) {
    SkASSERT(fHelpers.find(trackMe) < 0);
    *fHelpers.append() = trackMe;
}

void SkAnimateMaker::helperRemove(SkDisplayable* alreadyTracked) {
    int helperIndex = fHelpers.find(alreadyTracked);
    if (helperIndex >= 0)
        fHelpers.remove(helperIndex);
}

#if 0
void SkAnimateMaker::loadMovies() {
    for (SkDisplayable** dispPtr = fMovies.begin(); dispPtr < fMovies.end(); dispPtr++) {
        SkDisplayable* displayable = *dispPtr;
        SkASSERT(displayable->getType() == SkType_Movie);
        SkDisplayMovie* movie = (SkDisplayMovie*) displayable;
        SkAnimateMaker* movieMaker = movie->fMovie.fMaker;
        movieMaker->fEvents.doEvent(*movieMaker, SkDisplayEvent::kOnload, nullptr);
        movieMaker->fEvents.removeEvent(SkDisplayEvent::kOnload, nullptr);
        movieMaker->loadMovies();
    }
}
#endif

void SkAnimateMaker::notifyInval() {
    if (fHostEventSinkID)
        fAnimator->onEventPost(new SkEvent(SK_EventType_Inval), fHostEventSinkID);
}

void SkAnimateMaker::notifyInvalTime(SkMSec time) {
    if (fHostEventSinkID)
        fAnimator->onEventPostTime(new SkEvent(SK_EventType_Inval), fHostEventSinkID, time);
}

void SkAnimateMaker::postOnEnd(SkAnimateBase* animate, SkMSec end) {
        SkEvent evt;
        evt.setS32("time", animate->getStart() + end);
        evt.setPtr("anim", animate);
        evt.setType(SK_EventType_OnEnd);
        SkEventSinkID sinkID = fAnimator->getSinkID();
        fAnimator->onEventPost(new SkEvent(evt), sinkID);
}

void SkAnimateMaker::reset() {
    deleteMembers();
    fChildren.reset();
    fHelpers.reset();
    fIDs.reset();
    fEvents.reset();
    fDisplayList.hardReset();
}

void SkAnimateMaker::removeActive(SkActive* active) {
    if (active == nullptr)
        return;
    fDisplayList.remove(active);
}

bool SkAnimateMaker::resolveID(SkDisplayable* displayable, SkDisplayable* original) {
    SkString newID;
    bool success = computeID(original, nullptr, &newID);
    if (success)
        setID(displayable, newID);
    return success;
}

void SkAnimateMaker::setErrorString() {
    fErrorString.reset();
    if (fError.hasError()) {
        SkString err;
        if (fFileName.size() > 0)
            fErrorString.set(fFileName.c_str());
        else
            fErrorString.set("screenplay error");
        int line = fError.getLineNumber();
        if (line >= 0) {
            fErrorString.append(", ");
            fErrorString.append("line ");
            fErrorString.appendS32(line);
        }
        fErrorString.append(": ");
        fError.getErrorString(&err);
        fErrorString.append(err);
#if defined SK_DEBUG
        SkDebugf("%s\n", fErrorString.c_str());
#endif
    }
}

void SkAnimateMaker::setEnableTime(SkMSec appTime, SkMSec expectedTime) {
#if defined SK_DEBUG && defined SK_DEBUG_ANIMATION_TIMING
    SkString debugOut;
    SkMSec time = getAppTime();
    debugOut.appendS32(time - fDebugTimeBase);
    debugOut.append(" set enable old enable=");
    debugOut.appendS32(fEnableTime - fDebugTimeBase);
    debugOut.append(" old adjust=");
    debugOut.appendS32(fAdjustedStart);
    debugOut.append(" new enable=");
    debugOut.appendS32(expectedTime - fDebugTimeBase);
    debugOut.append(" new adjust=");
    debugOut.appendS32(appTime - expectedTime);
    SkDebugf("%s\n", debugOut.c_str());
#endif
    fAdjustedStart = appTime - expectedTime;
    fEnableTime = expectedTime;
    SkDisplayable** firstMovie = fMovies.begin();
    SkDisplayable** endMovie = fMovies.end();
    for (SkDisplayable** ptr = firstMovie; ptr < endMovie; ptr++) {
        SkDisplayMovie* movie = (SkDisplayMovie*) *ptr;
        movie->fMovie.fMaker->setEnableTime(appTime, expectedTime);
    }
}

void SkAnimateMaker::setExtraPropertyCallBack(SkDisplayTypes type,
        SkScriptEngine::_propertyCallBack callBack, void* userStorage) {
    SkExtras** end = fExtras.end();
    for (SkExtras** extraPtr = fExtras.begin(); extraPtr < end; extraPtr++) {
        SkExtras* extra = *extraPtr;
        if (extra->definesType(type)) {
            extra->fExtraCallBack = callBack;
            extra->fExtraStorage = userStorage;
            break;
        }
    }
}

void SkAnimateMaker::setID(SkDisplayable* displayable, const SkString& newID) {
    fIDs.set(newID.c_str(), displayable);
#ifdef SK_DEBUG
    displayable->_id.set(newID);
    displayable->id = displayable->_id.c_str();
#endif
}

void SkAnimateMaker::setScriptError(const SkScriptEngine& engine) {
    SkString errorString;
#ifdef SK_DEBUG
    engine.getErrorString(&errorString);
#endif
    setErrorNoun(errorString);
    setErrorCode(SkDisplayXMLParserError::kErrorInScript);
}

bool SkAnimateMaker::GetStep(const char* token, size_t len, void* stepPtr, SkScriptValue* value) {
    if (SK_LITERAL_STR_EQUAL("step", token, len)) {
        value->fOperand.fS32 = *(int32_t*) stepPtr;
        value->fType = SkType_Int;
        return true;
    }
    return false;
}