while (!res && it != _items.end())
        {
            res = res || (*it)->IsDirty();
            ++it;
        }

/***************************************************************
 * This source files comes from the xLights project
 * https://www.xlights.org
 * http://example.com
 * See the github commit history for a record of contributing
 * developers.
 * Copyright claimed based on commit dates recorded in Github
 * License: http://example.com
 **************************************************************/

#include "PlayListStep.h"

#include "PlayList.h"
#include "PlayListItemVideo.h"
#include "PlayListItemImage.h"
#include "PlayListItemJukebox.h"
#include "PlayListItemESEQ.h"
#include "PlayListItemFade.h"
#include "PlayListItemFile.h"
#include "PlayListItemFSEQ.h"
#include "PlayListItemText.h"
#include "PlayListItemScreenMap.h"
#include "PlayListItemFSEQVideo.h"
#include "PlayListItemTest.h"
#include "PlayListItemMicrophone.h"
#include "PlayListItemColourOrgan.h"
#include "PlayListItemRDS.h"
#include "PlayListItemProjector.h"
#include "PlayListItemAllOff.h"
#include "PlayListItemSetColour.h"
#include "PlayListItemDelay.h"
#include "PlayListItemDim.h"
#include "PlayListItemRunProcess.h"
#include "PlayListItemCURL.h"
#include "PlayListItemSerial.h"
#include "PlayListItemMIDI.h"
#include "PlayListItemFPPEvent.h"
#include "PlayListItemRunCommand.h"
#include "PlayListItemMQTT.h"
#include "PlayListItemOSC.h"
#include "PlayListItemAudio.h"
#include "PlayListItemARTNetTrigger.h"
#include "../xScheduleMain.h"
#include "../ScheduleManager.h"
#include "../ReentrancyCounter.h"
#include "../../xLights/UtilFunctions.h"
#include "../../xLights/FSEQFile.h"
#include "../ScheduleOptions.h"

#include <wx/filename.h>
#include <wx/xml/xml.h>

#include <log4cpp/Category.hh>

int __playliststepid = 0;

PlayListStep::PlayListStep(OutputManager* outputManager, wxXmlNode* node)
{
    _reentrancyCounter = 0;
    _loops = -1;
    _pause = 0;
    _suspend = 0;
    _id = __playliststepid++;
    _startTime = 0;
    _framecount = 0;
    _currentFrame = 0;
    _excludeFromRandom = false;
    _lastSavedChangeCount = 0;
    _changeCount = 0;
    _everyStep = false;
    Load(outputManager, node);
}

bool compare_priority(const PlayListItem* first, const PlayListItem* second)
{
    return first->GetPriority() > second->GetPriority();
}

PlayListStep::PlayListStep()
{
    _reentrancyCounter = 0;
    _loops = -1;
    _pause = 0;
    _suspend = 0;
    _id = __playliststepid++;
    _startTime = 0;
    _framecount = 0;
    _currentFrame = 0;
    _name = "";
    _lastSavedChangeCount = 0;
    _changeCount = 1;
    _excludeFromRandom = false;
    _everyStep = false;
}

PlayListStep::PlayListStep(const PlayListStep& step)
{
    _reentrancyCounter = 0;
    _startTime = 0;
    _pause = 0;
    _suspend = 0;
    _loops = step._loops;
    _framecount = step._framecount;
    _currentFrame = step._currentFrame;
    _name = step._name;
    _lastSavedChangeCount = step._lastSavedChangeCount;
    _changeCount = step._changeCount;
    _excludeFromRandom = step._excludeFromRandom;
    _everyStep = step._everyStep;
    _id = step._id;
    {
        ReentrancyCounter rec(_reentrancyCounter);
        for (auto it = step._items.begin(); it != step._items.end(); ++it)
        {
            _items.push_back((*it)->Copy());
        }
        _items.sort(compare_priority);
    }
}

void PlayListStep::ClearDirty()
{
    _lastSavedChangeCount = _changeCount;

    {
        ReentrancyCounter rec(_reentrancyCounter);
        for (auto it = _items.begin(); it != _items.end(); ++it)
        {
            (*it)->ClearDirty();
        }
    }
}

PlayListStep::~PlayListStep()
{
    static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base"));

    {
        ReentrancyCounter rec(_reentrancyCounter);

        if (!rec.SoleReference())
        {
            logger_base.warn("PlayListStep being destroyed but we appear to be manipulating it elsewhere. This may not end well.");
        }

        while (_items.size() > 0)
        {
            auto toremove = _items.front();
            _items.remove(toremove);
            delete toremove;
        }
    }
}

wxXmlNode* PlayListStep::Save()
{
    wxXmlNode* res = new wxXmlNode(nullptr, wxXML_ELEMENT_NODE, "PlayListStep");

    res->AddAttribute("Name", _name);
    if (_excludeFromRandom)
    {
        res->AddAttribute("ExcludeRandom", "TRUE");
    }
    if (_everyStep)
    {
        res->AddAttribute("EveryStep", "TRUE");
    }

    {
        ReentrancyCounter rec(_reentrancyCounter);
        for (auto it = _items.begin(); it != _items.end(); ++it)
        {
            res->AddChild((*it)->Save());
        }
    }

    return res;
}

void PlayListStep::Load(OutputManager* outputManager, wxXmlNode* node)
{
    _name = node->GetAttribute("Name", "");
    _excludeFromRandom = node->GetAttribute("ExcludeRandom", "FALSE") == "TRUE";
    _everyStep = node->GetAttribute("EveryStep", "FALSE") == "TRUE";

    for (wxXmlNode* n = node->GetChildren(); n != nullptr; n = n->GetNext())
    {
        if (n->GetName() == "PLIVideo")
        {
            _items.push_back(new PlayListItemVideo(n));
        }
        else if (n->GetName() == "PLIFSEQ")
        {
            _items.push_back(new PlayListItemFSEQ(outputManager, n));
        }
        else if (n->GetName() == "PLIFile")
        {
            _items.push_back(new PlayListItemFile(n));
        }
        else if (n->GetName() == "PLIText")
        {
            _items.push_back(new PlayListItemText(n));
        }
        else if (n->GetName() == "PLIScreenMap")
        {
            _items.push_back(new PlayListItemScreenMap(n));
        }
        else if (n->GetName() == "PLIAudio")
        {
            _items.push_back(new PlayListItemAudio(n));
        }
        else if (n->GetName() == "PLIARTNetTrigger")
        {
            _items.push_back(new PlayListItemARTNetTrigger(n));
        }
        else if (n->GetName() == "PLIFSEQVideo")
        {
            _items.push_back(new PlayListItemFSEQVideo(outputManager, n));
        }
        else if (n->GetName() == "PLITest")
        {
            _items.push_back(new PlayListItemTest(outputManager, n));
        }
        else if (n->GetName() == "PLIMicrophone")
        {
            _items.push_back(new PlayListItemMicrophone(outputManager, n));
        }
        else if (n->GetName() == "PLIColourOrgan")
        {
            _items.push_back(new PlayListItemColourOrgan(outputManager, n));
        }
        else if (n->GetName() == "PLIRDS")
        {
            _items.push_back(new PlayListItemRDS(n));
        }
        else if (n->GetName() == "PLIPJLink")
        {
            wxMessageBox("PJLink playlist item has been removed. Please re-create these using projector playlist item.");
            _items.push_back(new PlayListItemProjector());
        }
        else if (n->GetName() == "PLIProjector")
        {
            _items.push_back(new PlayListItemProjector(n));
        }
        else if (n->GetName() == "PLIESEQ")
        {
            _items.push_back(new PlayListItemESEQ(n));
        }
        else if (n->GetName() == "PLIFade")
        {
            _items.push_back(new PlayListItemFade(outputManager, n));
        }
        else if (n->GetName() == "PLIImage")
        {
            _items.push_back(new PlayListItemImage(n));
        }
        else if (n->GetName() == "PLIJukebox")
        {
            _items.push_back(new PlayListItemJukebox(n));
        }
        else if (n->GetName() == "PLIAllSet" || n->GetName() == "PLIAllOff")
        {
            _items.push_back(new PlayListItemAllOff(outputManager, n));
        }
        else if (n->GetName() == "PLISetColour")
        {
            _items.push_back(new PlayListItemSetColour(outputManager, n));
        }
        else if (n->GetName() == "PLIDelay")
        {
            _items.push_back(new PlayListItemDelay(n));
        }
        else if (n->GetName() == "PLIDim")
        {
            _items.push_back(new PlayListItemDim(outputManager, n));
        }
        else if (n->GetName() == "PLIProcess")
        {
            _items.push_back(new PlayListItemRunProcess(n));
        }
        else if (n->GetName() == "PLICURL")
        {
            _items.push_back(new PlayListItemCURL(n));
        }
        else if (n->GetName() == "PLISERIAL")
        {
            _items.push_back(new PlayListItemSerial(n));
        }
        else if (n->GetName() == "PLIMIDI")
        {
            _items.push_back(new PlayListItemMIDI(n));
        }
        else if (n->GetName() == "PLIFPPEVENT")
        {
            _items.push_back(new PlayListItemFPPEvent(n));
        }
        else if (n->GetName() == "PLICommand")
        {
            _items.push_back(new PlayListItemRunCommand(n));
        }
        else if (n->GetName() == "PLIMQTT")
        {
            _items.push_back(new PlayListItemMQTT(n));
        }
        else if (n->GetName() == "PLIOSC")
        {
            _items.push_back(new PlayListItemOSC(n));
        }
        else if (n->GetName() == "PLIVirtualMatrix")
        {
            // ignore this node ... these are not supported here any more.
        }
        else
        {
            wxASSERT(false);
        }
    }

    _items.sort(compare_priority);

    for (const auto& it : _items)
    {
        it->SetStepLength(GetLengthMS());
    }
}

bool PlayListStep::IsDirty()
{
    bool res = _lastSavedChangeCount != _changeCount;

    {
        ReentrancyCounter rec(_reentrancyCounter);
        auto it = _items.begin();

    }

    return res;
}

void PlayListStep::RemoveItem(PlayListItem* item)
{
    static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base"));

    {
        ReentrancyCounter rec(_reentrancyCounter);

        if (!rec.SoleReference())
        {
            logger_base.warn("PlayListStep removing an item but we appear to be manipulating it elsewhere. This may not end well.");
        }

        _items.remove(item);
    }
    _changeCount++;
}

std::string PlayListStep::GetStartTime(PlayList* pl)
{
    if (pl == nullptr) return "";

    return pl->GetStepStartTime(this);
}

std::string PlayListStep::GetName(PlayList* pl)
{
    std::string offset = "";
    if (pl != nullptr)
    {
        offset = " {" + pl->GetStepStartTime(this) + "}";
    }

    std::string duration = "";
    if (GetLengthMS() != 0)
    {
        duration = " [" + wxString::Format(wxT("%.3f"), (float)GetLengthMS() / 1000.0).ToStdString() + "]";
    }

    if (_name != "") return _name + offset + duration;

    {
        ReentrancyCounter rec(_reentrancyCounter);
        if (_items.size() == 0) return "<unnamed>" + offset + duration;

        return _items.front()->GetNameNoTime() + offset + duration;
    }
}

std::string PlayListStep::GetNameNoTime()
{
    if (_name != "") return _name;

    {
        ReentrancyCounter rec(_reentrancyCounter);
        if (_items.size() == 0) return "<unnamed>";

        return _items.front()->GetNameNoTime();
    }
}

PlayListItem* PlayListStep::GetTimeSource(size_t &ms) const
{
    ms = 9999;
    PlayListItem* timesource = nullptr;

    {
        ReentrancyCounter rec(_reentrancyCounter);
        // Prioritise the highest priotity FSEQ for timing
        for (auto it = _items.begin(); it != _items.end(); ++it)
        {
            if ((*it)->GetTitle() == "FSEQ" || (*it)->GetTitle() == "FSEQ & Video")
            {
                if ((*it)->GetDurationMS() > 0)
                {
                    size_t msec = (*it)->GetFrameMS();
                    if (timesource == nullptr)
                    {
                        timesource = *it;
                        ms = msec;
                    }
                    else
                    {
                        if (timesource != nullptr && (*it)->GetPriority() > timesource->GetPriority())
                        {
                            timesource = *it;
                            ms = msec;
                        }
                    }
                }
            }
        }

        if (timesource != nullptr)
        {
            if (ms == 9999)
            {
                ms = 50;
            }

            return timesource;
        }

        for (auto it = _items.begin(); it != _items.end(); ++it)
        {
            size_t msec = (*it)->GetFrameMS();

            if ((*it)->ControlsTiming())
            {
                if (timesource == nullptr && (*it)->GetDurationMS() > 0)
                {
                    timesource = *it;
                    ms = msec;
                }
                else
                {
                    if (timesource != nullptr && (*it)->GetDurationMS() > 0 && (*it)->GetPriority() > timesource->GetPriority())
                    {
                        timesource = *it;
                        ms = msec;
                    }
                }
            }
            else if (timesource == nullptr)
            {
                if (msec != 0 && msec < ms)
                {
                    ms = msec;
                }
            }
        }
    }

    if (ms == 9999)
    {
        ms = 50;
    }

    return timesource;
}

bool PlayListStep::Frame(uint8_t* buffer, size_t size, bool outputframe)
{
    ReentrancyCounter rec(_reentrancyCounter);

    static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base"));

    _currentFrame++;

    size_t msPerFrame = 1000;
    PlayListItem* timesource = GetTimeSource(msPerFrame);

    if (msPerFrame == 0)
    {
        msPerFrame = 50;
    }

    size_t frameMS;
    if (timesource != nullptr)
    {
        frameMS = timesource->GetPositionMS();
    }
    else
    {
        frameMS = wxGetUTCTimeMillis().GetLo() - _startTime;
    }

        // we are done

    wxStopWatch sw;
    // we do this backwards to ensure the right render order
    for (auto it = _items.rbegin(); it != _items.rend(); ++it)
    {
        (*it)->Frame(buffer, size, frameMS, msPerFrame, outputframe);
    }

    if (sw.Time() > (float)msPerFrame * 0.8)
    {
        logger_base.warn("Step %s frame %ld took longer than 80%% frame time to output: %ldms.", (const char *)GetNameNoTime().c_str(), (long)frameMS, (long)sw.Time());
    }

    if (timesource != nullptr)
    {
        return timesource->Done();
    }
    else
    {
        return frameMS >= GetLengthMS() - GetFrameMS();
    }
}

size_t PlayListStep::GetFrameMS()
{
    size_t ms = 0;
    GetTimeSource(ms);
    return ms;
}

void PlayListStep::Start(int loops)
{
    static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base"));
    logger_base.info("         ######## Playlist step %s starting.", (const char*)GetNameNoTime().c_str());

    _loops = loops;
    _startTime = wxGetUTCTimeMillis().GetLo();
    {
        ReentrancyCounter rec(_reentrancyCounter);
        for (auto it: _items)
        {
            it->Start(GetLengthMS());
        }
    }
}

void PlayListStep::Pause(bool pause)
{
    static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base"));

    if (pause)
    {
        _pause = wxGetUTCTimeMillis().GetLo();
        logger_base.info("                  Playlist step %s pausing.", (const char*)GetNameNoTime().c_str());
    }
    else
    {
        logger_base.info("                  Playlist step %s unpausing.", (const char*)GetNameNoTime().c_str());
        _pause = 0;
    }

    {
        ReentrancyCounter rec(_reentrancyCounter);
        for (auto it : _items)
        {
            it->Pause(pause);
        }
    }
}

void PlayListStep::Advance(int seconds)
{
    size_t msPerFrame = 1000;
    PlayListItem* timesource = GetTimeSource(msPerFrame);

    if (timesource != nullptr)
    {
        if (timesource->Advance(seconds)) return;
    }

    _startTime -= seconds * 1000;
    if (_startTime > wxGetUTCTimeMillis().GetLo()) _startTime = wxGetUTCTimeMillis().GetLo();
    if (wxGetUTCTimeMillis().GetLo() - _startTime > GetLengthMS()) _startTime = wxGetUTCTimeMillis().GetLo() - GetLengthMS();
}

std::string PlayListStep::GetFSEQTimeStamp() const
{
    auto fseq = GetActiveSyncItemFSEQ();

    if (fseq == "") return "";

    wxFileName fi(fseq);
    if (fi.Exists()) {

        auto fsf = FSEQFile::openFSEQFile(fseq);

        if (fsf != nullptr) {
            auto ch = fsf->getChannelCount();
            delete fsf;
            return wxString::Format("%s (%d)", fi.GetModificationTime().Format("%F %H:%M"), ch);
        }
        else             {
            return "FSEQ Invalid";
        }
    }
    return "FSEQ Not Found";
}

void PlayListStep::Suspend(bool suspend)
{
    static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base"));

    if (!IsPaused())
    {
        if (suspend)
        {
            _suspend = wxGetUTCTimeMillis().GetLo();
            logger_base.info("                  Playlist step %s suspending.", (const char*)GetNameNoTime().c_str());
        }
        else
        {
            logger_base.info("                  Playlist step %s unsuspending.", (const char*)GetNameNoTime().c_str());
            _suspend = 0;
        }
    }

    {
        ReentrancyCounter rec(_reentrancyCounter);
        for (auto it : _items)
        {
            it->Suspend(suspend);
        }
    }
}

void PlayListStep::Restart()
{
    static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base"));
    logger_base.info("Playlist step %s restarting.", (const char*)GetNameNoTime().c_str());

    _startTime = wxGetUTCTimeMillis().GetLo();
    {
        ReentrancyCounter rec(_reentrancyCounter);
        for (auto it : _items)
        {
            it->Restart();
        }
    }
}

void PlayListStep::Stop()
{
    static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base"));
    logger_base.info("         ######## Playlist step %s stopping.", (const char*)GetNameNoTime().c_str());

    {
        ReentrancyCounter rec(_reentrancyCounter);
        for (auto it : _items)
        {
            it->Stop();
        }
    }
}

size_t PlayListStep::GetPosition()
{
    size_t msPerFrame = 1000;
    PlayListItem* timesource = GetTimeSource(msPerFrame);

    size_t frameMS;
    if (timesource != nullptr)
    {
        frameMS = timesource->GetPositionMS();
    }
    else
    {
        auto now = wxGetUTCTimeMillis().GetLo();
        if (_pause == 0)
        {
            if (_suspend == 0)
            {
                frameMS = now - _startTime;
            }
            else
            {
                frameMS = now - _startTime - (now - _suspend);
            }
        }
        else
        {
            frameMS = now - _startTime - (now - _pause);
        }
    }

    wxASSERT(frameMS >= 0);
    wxASSERT(frameMS < 100000000);

    return frameMS;
}

size_t PlayListStep::GetLengthMS()
{
    size_t msPerFrame = 1000;
    PlayListItem* timesource = GetTimeSource(msPerFrame);
    if (timesource != nullptr)
    {
        wxASSERT(timesource->GetDurationMS() != 0);
        return timesource->GetDurationMS();
    }
    else
    {
        {
            ReentrancyCounter rec(_reentrancyCounter);
            size_t len = 0;
            for (auto it : _items)
            {
                // duration has to look valid
                if (it->GetDurationMS() < 999999999)
                {
                    len = std::max(len, it->GetDurationMS());
                }
            }

            if (len == 0)
            {
                for (auto it : _items)
                {
                    len = std::max(len, it->GetDurationMS(msPerFrame));
                }

                if (len == 0) len = msPerFrame;
            }
            wxASSERT(len != 0);
            return len;
        }
    }
}

std::string PlayListStep::GetActiveSyncItemFSEQ() const
{
    std::string res;
    size_t ms;
    auto ts = GetTimeSource(ms);

    if (ts != nullptr)
    {
        res = ts->GetSyncItemFSEQ();
    }

    if (res == "")
    {
        {
            ReentrancyCounter rec(_reentrancyCounter);
            for (auto it = _items.rbegin(); it != _items.rend(); ++it)
            {
                res = (*it)->GetSyncItemFSEQ();
                if (res != "") break;
            }
        }
    }

    return res;
}

std::string PlayListStep::GetActiveSyncItemMedia() const
{
    std::string res;
    size_t ms;
    auto ts = GetTimeSource(ms);

    if (ts != nullptr)
    {
        res = ts->GetSyncItemMedia();
    }

    if (res == "")
    {
        {
            ReentrancyCounter rec(_reentrancyCounter);
            for (auto it = _items.rbegin(); it != _items.rend(); ++it)
            {
                res = (*it)->GetSyncItemMedia();
                if (res != "") break;
            }
        }
    }

    return res;
}

bool PlayListStep::IsRunningFSEQ(const std::string& fseqFile)
{
    std::string fseq = GetActiveSyncItemFSEQ();
    wxFileName fn(fseq);

    return (fn.GetFullName().Lower() == wxString(fseqFile).Lower());
}

void PlayListStep::SetSyncPosition(size_t ms, size_t acceptableJitter, bool force)
{
    static log4cpp::Category& logger_sync = log4cpp::Category::getInstance(std::string("log_sync"));
    logger_sync.debug("SetSyncPosition: MS %ld Force %s.", (long)ms, force ? "true" : "false");

    bool handled = false;
    std::string fseq = GetActiveSyncItemFSEQ();

    {
        ReentrancyCounter rec(_reentrancyCounter);
        for (const auto& it : _items) {
            if (it->GetTitle() == "FSEQ") {
                PlayListItemFSEQ* pli = (PlayListItemFSEQ*)it;
                if (fseq == pli->GetFSEQFileName()) {
                    handled = true;
                    long posDiff = pli->GetPositionMS() - ms; // Stephen Morgan - Wrong Variable?

                    // double the acceptable jitter if we are ahead as this is more likely a network effect
                    // stops a jump back then big jump forward as often
                    if (posDiff < 0) acceptableJitter *= 2;

                    if (std::abs(posDiff) > acceptableJitter) {
                        int frame = ms / pli->GetFrameMS();
                        if (force) {
                            long timeDiff = (long)frame * (long)pli->GetFrameMS() - (long)pli->GetPositionMS();

                            if (timeDiff != 0) // if this is zero then we are less than one frame out
                            {
                                logger_sync.debug("Sync: Position was %d:%d - should be %d:%d: %ld:%ld. FORCED ReSync.", pli->GetCurrentFrame(), pli->GetPositionMS(), frame, frame * pli->GetFrameMS(), timeDiff, posDiff);

                                if (posDiff > (acceptableJitter * 2)) {
                                    pli->SetPosition(frame, ms);
                                    logger_sync.debug("Way OFF!! Need to SKIP (%ld).", timeDiff); // Add time to current position
                                }
                                else {
                                    long mscorrection = 0;
                                    int  fcorrection = 0;
                                    if (timeDiff > 0) {		// Ahead or Behind?
                                        mscorrection = (long)(acceptableJitter / 20);
                                        fcorrection = 1;
                                        logger_sync.debug("Behind:(%ld)- Need to move Forward - Correction(%ld).", posDiff, mscorrection); // Add time to current position
                                    }
                                    else {
                                        mscorrection = -(long)(acceptableJitter / 20);
                                        fcorrection = -1;
                                        logger_sync.debug("Ahead:(%ld)- Need to move Back - Correction(%ld).", posDiff, mscorrection); // Subtract time from current position
                                    }

                                    pli->SetPosition(pli->GetCurrentFrame() + fcorrection, pli->GetPositionMS() + mscorrection);
                                }

                                if (xScheduleFrame::GetScheduleManager() != nullptr)
                                    xScheduleFrame::GetScheduleManager()->SetTimerAdjustment(0);
                            }
                        }
                        else {
                            long timeDiff = (long)frame * (long)pli->GetFrameMS() - (long)pli->GetPositionMS();

                            // only adjust position if we are more that one frame out of sync
                            if (timeDiff != 0) {
                                int adjustment = 0;
                                if (abs(timeDiff) > pli->GetFrameMS() * 2) {
                                    adjustment = timeDiff / abs(timeDiff) * (int)((float)pli->GetFrameMS() * 0.1);
                                }
                                else if (abs(timeDiff) > pli->GetFrameMS()) {
                                    adjustment = timeDiff / abs(timeDiff) * (int)((float)pli->GetFrameMS() * 0.06);
                                }

                                logger_sync.debug("Sync: Position was %d:%d - should be %d:%d: %ld -> Adjustment to frame time %d.", pli->GetCurrentFrame(), pli->GetPositionMS(), frame, frame * pli->GetFrameMS(), timeDiff, adjustment);
                                if (xScheduleFrame::GetScheduleManager() != nullptr)
                                    xScheduleFrame::GetScheduleManager()->SetTimerAdjustment(adjustment);
                            }
                        }
                    }
                    break;
                }
            }
            else if (it->GetTitle() == "FSEQ & Video") {
                PlayListItemFSEQVideo* pli = (PlayListItemFSEQVideo*)it;
                if (fseq == pli->GetFSEQFileName()) {
                    handled = true;

                    long posDiff = pli->GetPositionMS() - ms; // Stephen Morgan - Wrong Variable?

                    // double the acceptable jitter if we are ahead as this is more likely a network effect
                    // stops a jump back then big jump forward as often
                    if (posDiff < 0) acceptableJitter *= 2;

                    if (std::abs(posDiff) > acceptableJitter) {
                        int frame = ms / pli->GetFrameMS();
                        if (force) {
                            long timeDiff = (long)frame * (long)pli->GetFrameMS() - (long)pli->GetPositionMS();
                            logger_sync.debug("Sync: Position was %d:%d - should be %d:%d: %ld. FORCED.", pli->GetCurrentFrame(), pli->GetPositionMS(), frame, frame * pli->GetFrameMS(), timeDiff);
                            pli->SetPosition(frame, ms);
                            if (xScheduleFrame::GetScheduleManager() != nullptr)
                                xScheduleFrame::GetScheduleManager()->SetTimerAdjustment(0);
                        }
                        else {
                            long timeDiff = (long)frame * (long)pli->GetFrameMS() - (long)pli->GetPositionMS();
                            // only adjust position if we are more that one frame out of sync

                            int adjustment = 0;
                            if (timeDiff == 0) {
                            }
                            else if (abs(timeDiff) > pli->GetFrameMS() * 2) {
                                adjustment = timeDiff / abs(timeDiff) * (int)((float)pli->GetFrameMS() * 0.1);
                            }
                            else if (abs(timeDiff) > pli->GetFrameMS()) {
                                adjustment = timeDiff / abs(timeDiff) * (int)((float)pli->GetFrameMS() * 0.06);
                            }

                            logger_sync.debug("Sync: Position was %d:%d - should be %d:%d: %ld -> Adjustment to frame time %d.", pli->GetCurrentFrame(), pli->GetPositionMS(), frame, frame * pli->GetFrameMS(), timeDiff, adjustment);

                            if (xScheduleFrame::GetScheduleManager() != nullptr)
                                xScheduleFrame::GetScheduleManager()->SetTimerAdjustment(adjustment);
                        }
                    }
                    break;
                }
            }
        }

        if (!handled) {

            long posDiff = GetPosition() - ms;

            // double the acceptable jitter if we are ahead as this is more likely a network effect
            // stops a jump back then big jump forward as often
            if (posDiff < 0) acceptableJitter *= 2;

            if (std::abs(posDiff) > acceptableJitter) {
                int frame = ms / GetFrameMS();
                if (force) {
                    long timeDiff = (long)frame * (long)GetFrameMS() - (long)GetPosition();
                    logger_sync.debug("Sync: Position was %d:%d - should be %d:%d: %ld. FORCED.", GetCurrentFrame(), GetPosition(), frame, frame * GetFrameMS(), timeDiff);
                    SetPosition(frame, ms);
                    if (xScheduleFrame::GetScheduleManager() != nullptr)
                        xScheduleFrame::GetScheduleManager()->SetTimerAdjustment(0);
                }
                else {
                    long timeDiff = (long)frame * (long)GetFrameMS() - (long)GetPosition();
                    // only adjust position if we are more that one frame out of sync

                    int adjustment = 0;
                    if (timeDiff == 0) {
                    }
                    else if (abs(timeDiff) > GetFrameMS() * 2) {
                        adjustment = timeDiff / abs(timeDiff) * (int)((float)GetFrameMS() * 0.1);
                    }
                    else if (abs(timeDiff) > GetFrameMS()) {
                        adjustment = timeDiff / abs(timeDiff) * (int)((float)GetFrameMS() * 0.06);
                    }

                    logger_sync.debug("Sync: Position was %d:%d - should be %d:%d: %ld -> Adjustment to frame time %d.", GetCurrentFrame(), GetPosition(), frame, frame * GetFrameMS(), timeDiff, adjustment);

                    if (xScheduleFrame::GetScheduleManager() != nullptr)
                        xScheduleFrame::GetScheduleManager()->SetTimerAdjustment(adjustment);
                }
            }
        }
    }
}

void PlayListStep::AdjustTime(wxTimeSpan by)
{
    _startTime += by.GetValue().GetLo();
}

std::string PlayListStep::FormatTime(size_t timems, bool ms) const
{
    if (ms)
    {
        return wxString::Format(wxT("%i:%02i.%03i"), (int)(timems / 60000), (int)((timems % 60000) / 1000), (int)(timems % 1000)).ToStdString();
    }
    else
    {
        return wxString::Format(wxT("%i:%02i"), (int)(timems / 60000), (int)((timems % 60000) / 1000)).ToStdString();
    }
}

std::string PlayListStep::GetStatus(bool ms)
{

    std::string fps = "Unknown";

    if (GetFrameMS() > 0)
    {
        fps = wxString::Format(wxT("%i"), (int)(1000 / GetFrameMS())).ToStdString();
    }

    std::string res = "Time: " + FormatTime(GetPosition(), ms) + " Left: " + FormatTime(GetLengthMS() - GetPosition(), ms) + " FPS: " + fps;

    return res;
}

std::list<PlayListItem*> PlayListStep::GetItems()
{
    {
        ReentrancyCounter rec(_reentrancyCounter);
        _items.sort(compare_priority);
        return _items;
    }
}

bool PlayListStep::IsSimple()
{
    {
        ReentrancyCounter rec(_reentrancyCounter);
        if (_items.size() == 0) return true;
        if (_items.size() != 1) return false;

        auto type = _items.front()->GetTitle();

        if (type == "FSEQ" || type == "Audio" || type == "FSEQ & Video") {
            return true;
        }
    }

    return false;
}

PlayListItemText* PlayListStep::GetTextItem(const std::string& name)
{
    {
        ReentrancyCounter rec(_reentrancyCounter);
        for (const auto& it : _items) {
            if (it->GetTitle() == "Text" && wxString(it->GetNameNoTime()).Lower() == wxString(name).Lower()) {
                return (PlayListItemText*)it;
            }
        }
    }

    return nullptr;
}

PlayListItem* PlayListStep::GetItem(const std::string item)
{
    {
        ReentrancyCounter rec(_reentrancyCounter);
        for (const auto& it : _items) {
            if (wxString(it->GetNameNoTime()).Lower() == wxString(item).Lower()) {
                return it;
            }
        }
    }

    return nullptr;
}

PlayListItem* PlayListStep::GetItem(const wxUint32 id)
{
    {
        ReentrancyCounter rec(_reentrancyCounter);
        for (const auto& it : _items) {
            if (it->GetId() == id) {
                return it;
            }
        }
    }

    return nullptr;
}

PlayListItem* PlayListStep::FindRunProcessNamed(const std::string& item)
{
    {
        ReentrancyCounter rec(_reentrancyCounter);
        for (const auto& it : _items)
        {
            if (it->GetTitle() == "Run Process" && wxString(it->GetNameNoTime()).Lower() == wxString(item).Lower())
            {
                return it;
            }
        }
    }

    return nullptr;
}

AudioManager* PlayListStep::GetAudioManager(PlayListItem* pli) const
{
    auto title = pli->GetTitle();
    if (title == "Audio")
    {
        PlayListItemAudio* p = (PlayListItemAudio*)pli;
        return p->GetAudioManager();
    }
    else if (title == "FSEQ")
    {
        PlayListItemFSEQ* p = (PlayListItemFSEQ*)pli;
        return p->GetAudioManager();
    }
    else if (title == "FSEQ & Video")
    {
        PlayListItemFSEQVideo* p = (PlayListItemFSEQVideo*)pli;
        return p->GetAudioManager();
    }

    return nullptr;
}

AudioManager* PlayListStep::GetAudioManager()
{
    size_t ms;
    auto pli = GetTimeSource(ms);

    if (pli != nullptr)
    {
        return GetAudioManager(pli);
    }
    else
    {
        {
            ReentrancyCounter rec(_reentrancyCounter);
            for (const auto& it : _items)
            {
                if (GetAudioManager(it) != nullptr)
                {
                    return GetAudioManager(it);
                }
            }
        }
    }

    return nullptr;
}

bool PlayListStep::SetPosition(size_t frame, size_t ms)
{
    _currentFrame = frame;

    size_t msPerFrame = 1000;
    PlayListItem* timesource = GetTimeSource(msPerFrame);
    if (timesource != nullptr) {
        // Set position should handled it
    }
    else {
        auto now = wxGetUTCTimeMillis().GetLo();
        if (_pause == 0) {
            if (_suspend == 0) {
                _startTime = now - ms;
            }
            else {
                _startTime = now - ms - (now - _suspend);
            }
        }
        else {
            _startTime = now - ms - (now - _pause);
        }
    }

    {
        ReentrancyCounter rec(_reentrancyCounter);
        for (const auto& it : _items) {
            it->SetPosition(frame, ms);
        }
    }

    return true;
}

int PlayListStep::GetStepIdFromName(const std::string& step)
{
    if (StartsWith(step, "**id="))
    {
        return wxAtoi(step.substr(5, step.size() - 7));
    }
    return -1;
}

std::string PlayListStep::GetStepNameWithId(int id)
{
    return wxString::Format("**id=%d**", id).ToStdString();
}


struct A {
  constexpr A() : n(1) {}
  ~A();
  int n;
};
struct B : A {
  A a[3];
  constexpr B() {
    ++a[0].n;
    a[1].n += 2;
    a[2].n = n + a[1].n;
  }
};
B b;

// CHECK-NOT: _ZN1BC

namespace ModifyStaticTemporary {
  struct A { int &&temporary; int x; };

{ r *= 9; return r - 12; }
  A a = { 6, f(a.temporary) };
  // CHECK: @_ZGRN21ModifyStaticTemporary1aE = private global i32 54

  A b = { 7, ++b.temporary };
  // CHECK: @_ZGRN21ModifyStaticTemporary1bE = private global i32 8

  // Can't emit all of 'c' as a constant here, so emit the initial value of
  // 'c.temporary', not the value as modified by the partial evaluation within
  // the initialization of 'c.x'.
  A c = { 10, (++c.temporary, b.x) };
  // CHECK: @_ZGRN21ModifyStaticTemporary1cE = private global i32 10
}

// CHECK: define
// CHECK-NOT: @_ZGRN21ModifyStaticTemporary1cE
// CHECK: add
// CHECK: store


struct A {
  constexpr A() : n(1) {}
  ~A();
  int n;
};
struct B : A {
  A a[3];
  constexpr B() {
    ++a[0].n;
    a[1].n += 2;
    a[2].n = n + a[1].n;
  }
};
B b;

// CHECK-NOT: _ZN1BC

namespace ModifyStaticTemporary {
  struct A { int &&temporary; int x; };
  constexpr int f(int &r)

  A a = { 6, f(a.temporary) };
  // CHECK: @_ZGRN21ModifyStaticTemporary1aE = private global i32 54

  A b = { 7, ++b.temporary };
  // CHECK: @_ZGRN21ModifyStaticTemporary1bE = private global i32 8

  // Can't emit all of 'c' as a constant here, so emit the initial value of
  // 'c.temporary', not the value as modified by the partial evaluation within
  // the initialization of 'c.x'.
  A c = { 10, (++c.temporary, b.x) };
  // CHECK: @_ZGRN21ModifyStaticTemporary1cE = private global i32 10
}

// CHECK: define
// CHECK-NOT: @_ZGRN21ModifyStaticTemporary1cE
// CHECK: add
// CHECK: store


struct A {
  constexpr A() : n(1) {}
  ~A();
  int n;
};
struct B : A {
  A a[3];
  constexpr B() {
    ++a[0].n;
    a[1].n += 2

    a[2].n = n + a[1].n;
  }
};
B b;

// CHECK-NOT: _ZN1BC

namespace ModifyStaticTemporary {
  struct A { int &&temporary; int x; };
  constexpr int f(int &r) { r *= 9; return r - 12; }
  A a = { 6, f(a.temporary) };
  // CHECK: @_ZGRN21ModifyStaticTemporary1aE = private global i32 54

  A b = { 7, ++b.temporary };
  // CHECK: @_ZGRN21ModifyStaticTemporary1bE = private global i32 8

  // Can't emit all of 'c' as a constant here, so emit the initial value of
  // 'c.temporary', not the value as modified by the partial evaluation within
  // the initialization of 'c.x'.
  A c = { 10, (++c.temporary, b.x) };
  // CHECK: @_ZGRN21ModifyStaticTemporary1cE = private global i32 10
}

// CHECK: define
// CHECK-NOT: @_ZGRN21ModifyStaticTemporary1cE
// CHECK: add
// CHECK: store


bool verifySEDM(const SEDM& sedm, const uint32_t* control)

#include <BWColor.h>
#include <BinaryImage.h>
#include <SEDM.h>

#include <QImage>
#include <boost/test/unit_test.hpp>
#include <cmath>
#include <iostream>

#include "Utils.h"

namespace imageproc {
namespace tests {
using namespace utils;

BOOST_AUTO_TEST_SUITE(SEDMTestSuite)


{
  const int width = size.width();
  const int height = size.height();
  for (int y = 0; y < height; ++y) {
    for (int x = 0; x < width; ++x, ++data) {
      std::cout << *data << ' ';
    }
    std::cout << std::endl;
  }
}

#include <BWColor.h>
#include <BinaryImage.h>
#include <SEDM.h>

#include <QImage>
#include <boost/test/unit_test.hpp>
#include <cmath>
#include <iostream>

#include "Utils.h"

namespace imageproc {
namespace tests {
using namespace utils;

BOOST_AUTO_TEST_SUITE(SEDMTestSuite)

bool verifySEDM(const SEDM& sedm, const uint32_t* control) {
  const uint32_t* line = sedm.data();
  for (int y = 0; y < sedm.size().height(); ++y) {
    for (int x = 0; x < sedm.size().width(); ++x) {
      if (line[x] != *control) {
        return false;
      }
      ++control;
    }
    line += sedm.stride();
  }
  return true;
}

void dumpMatrix(const uint32_t* data, QSize size)


#include <BWColor.h>
#include <BinaryImage.h>
#include <SEDM.h>

#include <QImage>
#include <boost/test/unit_test.hpp>
#include <cmath>
#include <iostream>

#include "Utils.h"

namespace imageproc {
namespace tests {
using namespace utils;

BOOST_AUTO_TEST_SUITE(SEDMTestSuite)

bool verifySEDM(const SEDM& sedm, const uint32_t* control) {
  const uint32_t* line = sedm.data();
  for (int y = 0; y < sedm.size().height(); ++y) {
    for (int x = 0; x < sedm.size().width(); ++x) {
      if (line[x] != *control) {
        return false;
      }
      ++control;
    }
    line += sedm.stride();
  }
  return true;
}

void dumpMatrix(const uint32_t* data, QSize size) {
  const int width = size.width();


for (int y = 0; y < height; ++y) {
    for (int x = 0; x < width; ++x, ++data) {
      std::cout << *data << ' ';
    }
    std::cout << std::endl;
  }
}

#include <BWColor.h>
#include <BinaryImage.h>
#include <SEDM.h>

#include <QImage>
#include <boost/test/unit_test.hpp>
#include <cmath>
#include <iostream>

#include "Utils.h"

namespace imageproc {
namespace tests {
using namespace utils;

BOOST_AUTO_TEST_SUITE(SEDMTestSuite)

bool verifySEDM(const SEDM& sedm, const uint32_t* control) {
  const uint32_t* line = sedm.data();
  for (int y = 0; y < sedm.size().height(); ++y) {
    for (int x = 0; x < sedm.size().width(); ++x) {
      if (line[x] != *control) {
        return false;
      }
      ++control;
    }
    line += sedm.stride();
  }
  return true;
}

void dumpMatrix(const uint32_t* data, QSize size) {
  const int width = size.width();
  const int height = size.height();


#include <BWColor.h>
#include <BinaryImage.h>
#include <SEDM.h>

#include <QImage>
#include <boost/test/unit_test.hpp>
#include <cmath>
#include <iostream>

#include "Utils.h"

namespace imageproc {
namespace tests {
using namespace utils;

BOOST_AUTO_TEST_SUITE(SEDMTestSuite)

bool verifySEDM(const SEDM& sedm, const uint32_t* control) {
  const uint32_t* line = sedm.data();
  for (int y = 0; y < sedm.size().height(); ++y) {
    for (int x = 0; x < sedm.size().width(); ++x) {
      if (line[x] != *control) {
        return false;
      }
      ++control;
    }
    line += sedm.stride();
  }
  return true;
}

void dumpMatrix(const uint32_t* data, QSize size) {
  const int width = size.width();
  const int height


if (line[x] != *control) {
        return false;
      }

#include <BWColor.h>
#include <BinaryImage.h>
#include <SEDM.h>

#include <QImage>
#include <boost/test/unit_test.hpp>
#include <cmath>
#include <iostream>

#include "Utils.h"

namespace imageproc {
namespace tests {
using namespace utils;

BOOST_AUTO_TEST_SUITE(SEDMTestSuite)

bool verifySEDM(const SEDM& sedm, const uint32_t* control) {
  const uint32_t* line = sedm.data();
  for (int y = 0; y < sedm.size().height(); ++y) {
    for (int x = 0; x < sedm.size().width(); ++x) {


for (int x = 0; x < sedm.size().width(); ++x) {
      if (line[x] != *control) {
        return false;
      }
      ++control;
    }

#include <BWColor.h>
#include <BinaryImage.h>
#include <SEDM.h>

#include <QImage>
#include <boost/test/unit_test.hpp>
#include <cmath>
#include <iostream>

#include "Utils.h"

namespace imageproc {
namespace tests {
using namespace utils;

BOOST_AUTO_TEST_SUITE(SEDMTestSuite)

bool verifySEDM(const SEDM& sedm, const uint32_t* control) {
  const uint32_t* line = sedm.data();
  for (int y = 0; y < sedm.size().height(); ++y) {


END_SHADER_PARAMS

	SHADER_INIT_PARAMS()

#include "BaseVSShader.h"

#define USE_NEW_SHADER //Updating assembly shaders to fxc, this is for A/B testing.

#ifdef USE_NEW_SHADER

#include "unlitgeneric_vs20.inc"
#include "unlitgeneric_ps20.inc"
#include "unlitgeneric_ps20b.inc"

#endif

#include "unlitgeneric_vs11.inc"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

BEGIN_VS_SHADER_FLAGS( DebugNormalMap, "Help for DebugNormalMap", SHADER_NOT_EDITABLE )

	BEGIN_SHADER_PARAMS
		SHADER_PARAM( BUMPMAP, SHADER_PARAM_TYPE_TEXTURE, "shadertest/WorldDiffuseBumpMap_bump", "bump map" )
		SHADER_PARAM( BUMPFRAME, SHADER_PARAM_TYPE_INTEGER, "0", "frame number for $bumpmap" )
		SHADER_PARAM( BUMPTRANSFORM, SHADER_PARAM_TYPE_MATRIX, "center .5 .5 scale 1 1 rotate 0 translate 0 0", "$bumpmap texcoord transform" )

{
		SET_FLAGS2( MATERIAL_VAR2_SUPPORTS_HW_SKINNING );
	}

	SHADER_INIT
	{
	}

	SHADER_FALLBACK
	{
		if( g_pHardwareConfig->GetDXSupportLevel() < 80 )
		{
			return "Wireframe";
		}
		return 0;
	}

	SHADER_DRAW
	{
		SHADOW_STATE
		{
			pShaderShadow->EnableTexture( SHADER_SAMPLER0, true );

			// Set stream format (note that this shader supports compression)
			unsigned int flags = VERTEX_POSITION | VERTEX_FORMAT_COMPRESSED;
			int nTexCoordCount = 1;
			int userDataSize = 0;
			pShaderShadow->VertexShaderVertexFormat( flags, nTexCoordCount, NULL, userDataSize );

#ifdef USE_NEW_SHADER
			if( g_pHardwareConfig->GetDXSupportLevel() >= 90 )
			{
				DECLARE_STATIC_VERTEX_SHADER( unlitgeneric_vs20 );
				SET_STATIC_VERTEX_SHADER( unlitgeneric_vs20 );

				if( g_pHardwareConfig->SupportsPixelShaders_2_b() )
				{
					DECLARE_STATIC_PIXEL_SHADER( unlitgeneric_ps20b );
					SET_STATIC_PIXEL_SHADER( unlitgeneric_ps20b );
				}
				else
				{
					DECLARE_STATIC_PIXEL_SHADER( unlitgeneric_ps20 );
					SET_STATIC_PIXEL_SHADER( unlitgeneric_ps20 );
				}
			}
			else
#endif
			{
				unlitgeneric_vs11_Static_Index vshIndex;
				vshIndex.SetDETAIL( false );
				vshIndex.SetENVMAP( false );
				vshIndex.SetENVMAPCAMERASPACE( false );
				vshIndex.SetENVMAPSPHERE( false );
				vshIndex.SetVERTEXCOLOR( false );
				vshIndex.SetSEPARATEDETAILUVS( false );
				pShaderShadow->SetVertexShader( "unlitgeneric_vs11", vshIndex.GetIndex() );

				pShaderShadow->SetPixelShader( "unlitgeneric" );
			}
		}
		DYNAMIC_STATE
		{
			if ( params[BUMPMAP]->IsTexture() )
			{
				BindTexture( SHADER_SAMPLER0, BUMPMAP, BUMPFRAME );
			}
			else
			{
				pShaderAPI->BindStandardTexture( SHADER_SAMPLER0, TEXTURE_NORMALMAP_FLAT );
			}
			SetVertexShaderTextureTransform( VERTEX_SHADER_SHADER_SPECIFIC_CONST_0, BUMPTRANSFORM );
#ifdef USE_NEW_SHADER
			if( g_pHardwareConfig->GetDXSupportLevel() >= 90 )
			{
				BOOL bShaderConstants[1] = { false };
				pShaderAPI->SetBooleanVertexShaderConstant( VERTEX_SHADER_SHADER_SPECIFIC_BOOL_CONST_0, bShaderConstants, 1 );

				DECLARE_DYNAMIC_VERTEX_SHADER( unlitgeneric_vs20 );
				SET_DYNAMIC_VERTEX_SHADER_COMBO( DOWATERFOG, pShaderAPI->GetSceneFogMode() == MATERIAL_FOG_LINEAR_BELOW_FOG_Z );
				SET_DYNAMIC_VERTEX_SHADER_COMBO( SKINNING, pShaderAPI->GetCurrentNumBones() > 0 );
				SET_DYNAMIC_VERTEX_SHADER_COMBO( COMPRESSED_VERTS, (int)vertexCompression );
				SET_DYNAMIC_VERTEX_SHADER( unlitgeneric_vs20 );

				if( g_pHardwareConfig->SupportsPixelShaders_2_b() )
				{
					DECLARE_DYNAMIC_PIXEL_SHADER( unlitgeneric_ps20b );
					SET_DYNAMIC_PIXEL_SHADER( unlitgeneric_ps20b );
				}
				else
				{
					DECLARE_DYNAMIC_PIXEL_SHADER( unlitgeneric_ps20 );
					SET_DYNAMIC_PIXEL_SHADER( unlitgeneric_ps20 );
				}
			}
			else
#endif
			{
				unlitgeneric_vs11_Dynamic_Index vshIndex;
				vshIndex.SetDOWATERFOG( pShaderAPI->GetSceneFogMode() == MATERIAL_FOG_LINEAR_BELOW_FOG_Z );
				vshIndex.SetSKINNING( pShaderAPI->GetCurrentNumBones() > 0 );
				pShaderAPI->SetVertexShaderIndex( vshIndex.GetIndex() );
			}
		}
		Draw();
	}
END_SHADER


{
		SET_FLAGS2( MATERIAL_VAR2_SUPPORTS_HW_SKINNING );
	}

#include "BaseVSShader.h"

#define USE_NEW_SHADER //Updating assembly shaders to fxc, this is for A/B testing.

#ifdef USE_NEW_SHADER

#include "unlitgeneric_vs20.inc"
#include "unlitgeneric_ps20.inc"
#include "unlitgeneric_ps20b.inc"

#endif

#include "unlitgeneric_vs11.inc"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

BEGIN_VS_SHADER_FLAGS( DebugNormalMap, "Help for DebugNormalMap", SHADER_NOT_EDITABLE )

	BEGIN_SHADER_PARAMS
		SHADER_PARAM( BUMPMAP, SHADER_PARAM_TYPE_TEXTURE, "shadertest/WorldDiffuseBumpMap_bump", "bump map" )
		SHADER_PARAM( BUMPFRAME, SHADER_PARAM_TYPE_INTEGER, "0", "frame number for $bumpmap" )
		SHADER_PARAM( BUMPTRANSFORM, SHADER_PARAM_TYPE_MATRIX, "center .5 .5 scale 1 1 rotate 0 translate 0 0", "$bumpmap texcoord transform" )
	END_SHADER_PARAMS

	SHADER_INIT_PARAMS()

	SHADER_INIT
	{
	}

	SHADER_FALLBACK
	{
		if( g_pHardwareConfig->GetDXSupportLevel() < 80 )
		{
			return "Wireframe";
		}
		return 0;
	}

	SHADER_DRAW
	{
		SHADOW_STATE
		{
			pShaderShadow->EnableTexture( SHADER_SAMPLER0, true );

			// Set stream format (note that this shader supports compression)
			unsigned int flags = VERTEX_POSITION | VERTEX_FORMAT_COMPRESSED;
			int nTexCoordCount = 1;
			int userDataSize = 0;
			pShaderShadow->VertexShaderVertexFormat( flags, nTexCoordCount, NULL, userDataSize );

#ifdef USE_NEW_SHADER
			if( g_pHardwareConfig->GetDXSupportLevel() >= 90 )
			{
				DECLARE_STATIC_VERTEX_SHADER( unlitgeneric_vs20 );
				SET_STATIC_VERTEX_SHADER( unlitgeneric_vs20 );

				if( g_pHardwareConfig->SupportsPixelShaders_2_b() )
				{
					DECLARE_STATIC_PIXEL_SHADER( unlitgeneric_ps20b );
					SET_STATIC_PIXEL_SHADER( unlitgeneric_ps20b );
				}
				else
				{
					DECLARE_STATIC_PIXEL_SHADER( unlitgeneric_ps20 );
					SET_STATIC_PIXEL_SHADER( unlitgeneric_ps20 );
				}
			}
			else
#endif
			{
				unlitgeneric_vs11_Static_Index vshIndex;
				vshIndex.SetDETAIL( false );
				vshIndex.SetENVMAP( false );
				vshIndex.SetENVMAPCAMERASPACE( false );
				vshIndex.SetENVMAPSPHERE( false );
				vshIndex.SetVERTEXCOLOR( false );
				vshIndex.SetSEPARATEDETAILUVS( false );
				pShaderShadow->SetVertexShader( "unlitgeneric_vs11", vshIndex.GetIndex() );

				pShaderShadow->SetPixelShader( "unlitgeneric" );
			}
		}
		DYNAMIC_STATE
		{
			if ( params[BUMPMAP]->IsTexture() )
			{
				BindTexture( SHADER_SAMPLER0, BUMPMAP, BUMPFRAME );
			}
			else
			{
				pShaderAPI->BindStandardTexture( SHADER_SAMPLER0, TEXTURE_NORMALMAP_FLAT );
			}
			SetVertexShaderTextureTransform( VERTEX_SHADER_SHADER_SPECIFIC_CONST_0, BUMPTRANSFORM );
#ifdef USE_NEW_SHADER
			if( g_pHardwareConfig->GetDXSupportLevel() >= 90 )
			{
				BOOL bShaderConstants[1] = { false };
				pShaderAPI->SetBooleanVertexShaderConstant( VERTEX_SHADER_SHADER_SPECIFIC_BOOL_CONST_0, bShaderConstants, 1 );

				DECLARE_DYNAMIC_VERTEX_SHADER( unlitgeneric_vs20 );
				SET_DYNAMIC_VERTEX_SHADER_COMBO( DOWATERFOG, pShaderAPI->GetSceneFogMode() == MATERIAL_FOG_LINEAR_BELOW_FOG_Z );
				SET_DYNAMIC_VERTEX_SHADER_COMBO( SKINNING, pShaderAPI->GetCurrentNumBones() > 0 );
				SET_DYNAMIC_VERTEX_SHADER_COMBO( COMPRESSED_VERTS, (int)vertexCompression );
				SET_DYNAMIC_VERTEX_SHADER( unlitgeneric_vs20 );

				if( g_pHardwareConfig->SupportsPixelShaders_2_b() )
				{
					DECLARE_DYNAMIC_PIXEL_SHADER( unlitgeneric_ps20b );
					SET_DYNAMIC_PIXEL_SHADER( unlitgeneric_ps20b );
				}
				else
				{
					DECLARE_DYNAMIC_PIXEL_SHADER( unlitgeneric_ps20 );
					SET_DYNAMIC_PIXEL_SHADER( unlitgeneric_ps20 );
				}
			}
			else
#endif
			{
				unlitgeneric_vs11_Dynamic_Index vshIndex;
				vshIndex.SetDOWATERFOG( pShaderAPI->GetSceneFogMode() == MATERIAL_FOG_LINEAR_BELOW_FOG_Z );
				vshIndex.SetSKINNING( pShaderAPI->GetCurrentNumBones() > 0 );
				pShaderAPI->SetVertexShaderIndex( vshIndex.GetIndex() );
			}
		}
		Draw();
	}
END_SHADER


#include "BaseVSShader.h"

#define USE_NEW_SHADER //Updating assembly shaders to fxc, this is for A/B testing.

#ifdef USE_NEW_SHADER

#include "unlitgeneric_vs20.inc"
#include "unlitgeneric_ps20.inc"
#include "unlitgeneric_ps20b.inc"

#endif

#include "unlitgeneric_vs11.inc"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

BEGIN_VS_SHADER_FLAGS( DebugNormalMap, "Help for DebugNormalMap", SHADER_NOT_EDITABLE )

	BEGIN_SHADER_PARAMS
		SHADER_PARAM( BUMPMAP, SHADER_PARAM_TYPE_TEXTURE, "shadertest/WorldDiffuseBumpMap_bump", "bump map" )
		SHADER_PARAM( BUMPFRAME, SHADER_PARAM_TYPE_INTEGER, "0", "frame number for $bumpmap" )
		SHADER_PARAM( BUMPTRANSFORM, SHADER_PARAM_TYPE_MATRIX, "center .5 .5 scale 1 1 rotate 0 translate 0 0", "$bumpmap texcoord transform" )
	END_SHADER_PARAMS

	SHADER_INIT_PARAMS()
	{
		SET_FLAGS2( MATERIAL

	}

	SHADER_INIT
	{
	}

	SHADER_FALLBACK
	{
		if( g_pHardwareConfig->GetDXSupportLevel() < 80 )
		{
			return "Wireframe";
		}
		return 0;
	}

	SHADER_DRAW
	{
		SHADOW_STATE
		{
			pShaderShadow->EnableTexture( SHADER_SAMPLER0, true );

			// Set stream format (note that this shader supports compression)
			unsigned int flags = VERTEX_POSITION | VERTEX_FORMAT_COMPRESSED;
			int nTexCoordCount = 1;
			int userDataSize = 0;
			pShaderShadow->VertexShaderVertexFormat( flags, nTexCoordCount, NULL, userDataSize );

#ifdef USE_NEW_SHADER
			if( g_pHardwareConfig->GetDXSupportLevel() >= 90 )
			{
				DECLARE_STATIC_VERTEX_SHADER( unlitgeneric_vs20 );
				SET_STATIC_VERTEX_SHADER( unlitgeneric_vs20 );

				if( g_pHardwareConfig->SupportsPixelShaders_2_b() )
				{
					DECLARE_STATIC_PIXEL_SHADER( unlitgeneric_ps20b );
					SET_STATIC_PIXEL_SHADER( unlitgeneric_ps20b );
				}
				else
				{
					DECLARE_STATIC_PIXEL_SHADER( unlitgeneric_ps20 );
					SET_STATIC_PIXEL_SHADER( unlitgeneric_ps20 );
				}
			}
			else
#endif
			{
				unlitgeneric_vs11_Static_Index vshIndex;
				vshIndex.SetDETAIL( false );
				vshIndex.SetENVMAP( false );
				vshIndex.SetENVMAPCAMERASPACE( false );
				vshIndex.SetENVMAPSPHERE( false );
				vshIndex.SetVERTEXCOLOR( false );
				vshIndex.SetSEPARATEDETAILUVS( false );
				pShaderShadow->SetVertexShader( "unlitgeneric_vs11", vshIndex.GetIndex() );

				pShaderShadow->SetPixelShader( "unlitgeneric" );
			}
		}
		DYNAMIC_STATE
		{
			if ( params[BUMPMAP]->IsTexture() )
			{
				BindTexture( SHADER_SAMPLER0, BUMPMAP, BUMPFRAME );
			}
			else
			{
				pShaderAPI->BindStandardTexture( SHADER_SAMPLER0, TEXTURE_NORMALMAP_FLAT );
			}
			SetVertexShaderTextureTransform( VERTEX_SHADER_SHADER_SPECIFIC_CONST_0, BUMPTRANSFORM );
#ifdef USE_NEW_SHADER
			if( g_pHardwareConfig->GetDXSupportLevel() >= 90 )
			{
				BOOL bShaderConstants[1] = { false };
				pShaderAPI->SetBooleanVertexShaderConstant( VERTEX_SHADER_SHADER_SPECIFIC_BOOL_CONST_0, bShaderConstants, 1 );

				DECLARE_DYNAMIC_VERTEX_SHADER( unlitgeneric_vs20 );
				SET_DYNAMIC_VERTEX_SHADER_COMBO( DOWATERFOG, pShaderAPI->GetSceneFogMode() == MATERIAL_FOG_LINEAR_BELOW_FOG_Z );
				SET_DYNAMIC_VERTEX_SHADER_COMBO( SKINNING, pShaderAPI->GetCurrentNumBones() > 0 );
				SET_DYNAMIC_VERTEX_SHADER_COMBO( COMPRESSED_VERTS, (int)vertexCompression );
				SET_DYNAMIC_VERTEX_SHADER( unlitgeneric_vs20 );

				if( g_pHardwareConfig->SupportsPixelShaders_2_b() )
				{
					DECLARE_DYNAMIC_PIXEL_SHADER( unlitgeneric_ps20b );
					SET_DYNAMIC_PIXEL_SHADER( unlitgeneric_ps20b );
				}
				else
				{
					DECLARE_DYNAMIC_PIXEL_SHADER( unlitgeneric_ps20 );
					SET_DYNAMIC_PIXEL_SHADER( unlitgeneric_ps20 );
				}
			}
			else
#endif
			{
				unlitgeneric_vs11_Dynamic_Index vshIndex;
				vshIndex.SetDOWATERFOG( pShaderAPI->GetSceneFogMode() == MATERIAL_FOG_LINEAR_BELOW_FOG_Z );
				vshIndex.SetSKINNING( pShaderAPI->GetCurrentNumBones() > 0 );
				pShaderAPI->SetVertexShaderIndex( vshIndex.GetIndex() );
			}
		}
		Draw();
	}
END_SHADER


if( g_pHardwareConfig->GetDXSupportLevel() >= 90 )
			{
				DECLARE_STATIC_VERTEX_SHADER( unlitgeneric_vs20 );
				SET_STATIC_VERTEX_SHADER( unlitgeneric_vs20 );

				if( g_pHardwareConfig->SupportsPixelShaders_2_b() )
				{
					DECLARE_STATIC_PIXEL_SHADER( unlitgeneric_ps20b );
					SET_STATIC_PIXEL_SHADER( unlitgeneric_ps20b );
				}
				else
				{
					DECLARE_STATIC_PIXEL_SHADER( unlitgeneric_ps20 );
					SET_STATIC_PIXEL_SHADER( unlitgeneric_ps20 );
				}
			}

#include "BaseVSShader.h"

#define USE_NEW_SHADER //Updating assembly shaders to fxc, this is for A/B testing.

#ifdef USE_NEW_SHADER

#include "unlitgeneric_vs20.inc"
#include "unlitgeneric_ps20.inc"
#include "unlitgeneric_ps20b.inc"

#endif

#include "unlitgeneric_vs11.inc"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

BEGIN_VS_SHADER_FLAGS( DebugNormalMap, "Help for DebugNormalMap", SHADER_NOT_EDITABLE )

	BEGIN_SHADER_PARAMS
		SHADER_PARAM( BUMPMAP, SHADER_PARAM_TYPE_TEXTURE, "shadertest/WorldDiffuseBumpMap_bump", "bump map" )
		SHADER_PARAM( BUMPFRAME, SHADER_PARAM_TYPE_INTEGER, "0", "frame number for $bumpmap" )
		SHADER_PARAM( BUMPTRANSFORM, SHADER_PARAM_TYPE_MATRIX, "center .5 .5 scale 1 1 rotate 0 translate 0 0", "$bumpmap texcoord transform" )
	END_SHADER_PARAMS

	SHADER_INIT_PARAMS()
	{
		SET_FLAGS2( MATERIAL_VAR2_SUPPORTS_HW_SKINNING );
	}

	SHADER_INIT
	{
	}

	SHADER_FALLBACK
	{
		if( g_pHardwareConfig->GetDXSupportLevel() < 80 )
		{
			return "Wireframe";
		}
		return 0;
	}

	SHADER_DRAW
	{
		SHADOW_STATE
		{
			pShaderShadow->EnableTexture( SHADER_SAMPLER0, true );

			// Set stream format (note that this shader supports compression)
			unsigned int flags = VERTEX_POSITION | VERTEX_FORMAT_COMPRESSED;
			int nTexCoordCount = 1;
			int userDataSize = 0;
			pShaderShadow->VertexShaderVertexFormat( flags, nTexCoordCount, NULL, userDataSize );

#ifdef USE_NEW_SHADER

			else
#endif
			{
				unlitgeneric_vs11_Static_Index vshIndex;
				vshIndex.SetDETAIL( false );
				vshIndex.SetENVMAP( false );
				vshIndex.SetENVMAPCAMERASPACE( false );
				vshIndex.SetENVMAPSPHERE( false );
				vshIndex.SetVERTEXCOLOR( false );
				vshIndex.SetSEPARATEDETAILUVS( false );
				pShaderShadow->SetVertexShader( "unlitgeneric_vs11", vshIndex.GetIndex() );

				pShaderShadow->SetPixelShader( "unlitgeneric" );
			}
		}
		DYNAMIC_STATE
		{
			if ( params[BUMPMAP]->IsTexture() )
			{
				BindTexture( SHADER_SAMPLER0, BUMPMAP, BUMPFRAME );
			}
			else
			{
				pShaderAPI->BindStandardTexture( SHADER_SAMPLER0, TEXTURE_NORMALMAP_FLAT );
			}
			SetVertexShaderTextureTransform( VERTEX_SHADER_SHADER_SPECIFIC_CONST_0, BUMPTRANSFORM );
#ifdef USE_NEW_SHADER
			if( g_pHardwareConfig->GetDXSupportLevel() >= 90 )
			{
				BOOL bShaderConstants[1] = { false };
				pShaderAPI->SetBooleanVertexShaderConstant( VERTEX_SHADER_SHADER_SPECIFIC_BOOL_CONST_0, bShaderConstants, 1 );

				DECLARE_DYNAMIC_VERTEX_SHADER( unlitgeneric_vs20 );
				SET_DYNAMIC_VERTEX_SHADER_COMBO( DOWATERFOG, pShaderAPI->GetSceneFogMode() == MATERIAL_FOG_LINEAR_BELOW_FOG_Z );
				SET_DYNAMIC_VERTEX_SHADER_COMBO( SKINNING, pShaderAPI->GetCurrentNumBones() > 0 );
				SET_DYNAMIC_VERTEX_SHADER_COMBO( COMPRESSED_VERTS, (int)vertexCompression );
				SET_DYNAMIC_VERTEX_SHADER( unlitgeneric_vs20 );

				if( g_pHardwareConfig->SupportsPixelShaders_2_b() )
				{
					DECLARE_DYNAMIC_PIXEL_SHADER( unlitgeneric_ps20b );
					SET_DYNAMIC_PIXEL_SHADER( unlitgeneric_ps20b );
				}
				else
				{
					DECLARE_DYNAMIC_PIXEL_SHADER( unlitgeneric_ps20 );
					SET_DYNAMIC_PIXEL_SHADER( unlitgeneric_ps20 );
				}
			}
			else
#endif
			{
				unlitgeneric_vs11_Dynamic_Index vshIndex;
				vshIndex.SetDOWATERFOG( pShaderAPI->GetSceneFogMode() == MATERIAL_FOG_LINEAR_BELOW_FOG_Z );
				vshIndex.SetSKINNING( pShaderAPI->GetCurrentNumBones() > 0 );
				pShaderAPI->SetVertexShaderIndex( vshIndex.GetIndex() );
			}
		}
		Draw();
	}
END_SHADER


DEFINE_API_IMPL( transaction_status_api_impl, find_transaction )

#include <hive/plugins/transaction_status_api/transaction_status_api_plugin.hpp>
#include <hive/plugins/transaction_status_api/transaction_status_api.hpp>

#include <hive/plugins/transaction_status/transaction_status_objects.hpp>

namespace hive { namespace plugins { namespace transaction_status_api {

namespace detail {

class transaction_status_api_impl
{
public:
  transaction_status_api_impl() :
    _db( appbase::app().get_plugin< hive::plugins::chain::chain_plugin >().db() ),
    _tsp( appbase::app().get_plugin< hive::plugins::transaction_status::transaction_status_plugin >() ) {}

  DECLARE_API_IMPL( (find_transaction) )

  chain::database& _db;
  transaction_status::transaction_status_plugin& _tsp;
};

{
  uint32_t earliest_tracked_block_num = _tsp.earliest_tracked_block_num();

  // Have we begun tracking?
  if ( _db.head_block_num() >= earliest_tracked_block_num )
  {
    auto last_irreversible_block_num = _db.get_last_irreversible_block_num();
    auto tso = _db.find< transaction_status::transaction_status_object, transaction_status::by_trx_id >( args.transaction_id );

    // If we are actively tracking this transaction
    if ( tso != nullptr)
    {
      // If we're not within a block
      if ( !tso->block_num )
        return {
          .status = transaction_status::within_mempool
        };

      // If we're in an irreversible block
      if ( tso->block_num <= last_irreversible_block_num )
        return {
          .status = transaction_status::within_irreversible_block,
          .block_num = tso->block_num
        };
      // We're in a reversible block
      else
        return {
          .status = transaction_status::within_reversible_block,
          .block_num = tso->block_num
        };
    }

    // If the user has provided us with an expiration
    if ( args.expiration.valid() )
    {
      const auto& expiration = *args.expiration;

      // Check if the expiration is before our earliest tracked block plus maximum transaction expiration
      auto earliest_tracked_block = _db.fetch_block_by_number( earliest_tracked_block_num );
      if ( expiration < earliest_tracked_block->timestamp + HIVE_MAX_TIME_UNTIL_EXPIRATION )
        return {
          .status = transaction_status::too_old
        };

      // If the expiration is on or before our last irreversible block
      if ( last_irreversible_block_num > 0 )
      {
        auto last_irreversible_block = _db.fetch_block_by_number( last_irreversible_block_num );
        if ( expiration <= last_irreversible_block->timestamp )
          return {
            .status = transaction_status::expired_irreversible
          };
      }
      // If our expiration is in the past
      if ( expiration < _db.head_block_time() )
        return {
          .status = transaction_status::expired_reversible
        };
    }
  }

  // Either the user did not provide an expiration or it is not expired and we didn't hear about this transaction
  return { .status = transaction_status::unknown };
}

} // hive::plugins::transaction_status_api::detail

transaction_status_api::transaction_status_api() : my( std::make_unique< detail::transaction_status_api_impl >() )
{
  JSON_RPC_REGISTER_API( HIVE_TRANSACTION_STATUS_API_PLUGIN_NAME );
}

transaction_status_api::~transaction_status_api() {}

DEFINE_READ_APIS( transaction_status_api, (find_transaction) )

} } } // hive::plugins::transaction_status_api


#include <hive/plugins/transaction_status_api/transaction_status_api_plugin.hpp>
#include <hive/plugins/transaction_status_api/transaction_status_api.hpp>

#include <hive/plugins/transaction_status/transaction_status_objects.hpp>

namespace hive { namespace plugins { namespace transaction_status_api {

namespace detail {

class transaction_status_api_impl
{
public:
  transaction_status_api_impl() :
    _db( appbase::app().get_plugin< hive::plugins::chain::chain_plugin >().db() ),
    _tsp( appbase::app().get_plugin< hive::plugins::transaction_status::transaction_status_plugin >() )

  DECLARE_API_IMPL( (find_transaction) )

  chain::database& _db;
  transaction_status::transaction_status_plugin& _tsp;
};

DEFINE_API_IMPL( transaction_status_api_impl, find_transaction )
{
  uint32_t earliest_tracked_block_num = _tsp.earliest_tracked_block_num();

  // Have we begun tracking?
  if ( _db.head_block_num() >= earliest_tracked_block_num )
  {
    auto last_irreversible_block_num = _db.get_last_irreversible_block_num();
    auto tso = _db.find< transaction_status::transaction_status_object, transaction_status::by_trx_id >( args.transaction_id );

    // If we are actively tracking this transaction
    if ( tso != nullptr)
    {
      // If we're not within a block
      if ( !tso->block_num )
        return {
          .status = transaction_status::within_mempool
        };

      // If we're in an irreversible block
      if ( tso->block_num <= last_irreversible_block_num )
        return {
          .status = transaction_status::within_irreversible_block,
          .block_num = tso->block_num
        };
      // We're in a reversible block
      else
        return {
          .status = transaction_status::within_reversible_block,
          .block_num = tso->block_num
        };
    }

    // If the user has provided us with an expiration
    if ( args.expiration.valid() )
    {
      const auto& expiration = *args.expiration;

      // Check if the expiration is before our earliest tracked block plus maximum transaction expiration
      auto earliest_tracked_block = _db.fetch_block_by_number( earliest_tracked_block_num );
      if ( expiration < earliest_tracked_block->timestamp + HIVE_MAX_TIME_UNTIL_EXPIRATION )
        return {
          .status = transaction_status::too_old
        };

      // If the expiration is on or before our last irreversible block
      if ( last_irreversible_block_num > 0 )
      {
        auto last_irreversible_block = _db.fetch_block_by_number( last_irreversible_block_num );
        if ( expiration <= last_irreversible_block->timestamp )
          return {
            .status = transaction_status::expired_irreversible
          };
      }
      // If our expiration is in the past
      if ( expiration < _db.head_block_time() )
        return {
          .status = transaction_status::expired_reversible
        };
    }
  }

  // Either the user did not provide an expiration or it is not expired and we didn't hear about this transaction
  return { .status = transaction_status::unknown };
}

} // hive::plugins::transaction_status_api::detail

transaction_status_api::transaction_status_api() : my( std::make_unique< detail::transaction_status_api_impl >() )
{
  JSON_RPC_REGISTER_API( HIVE_TRANSACTION_STATUS_API_PLUGIN_NAME );
}

transaction_status_api::~transaction_status_api() {}

DEFINE_READ_APIS( transaction_status_api, (find_transaction) )

} } } // hive::plugins::transaction_status_api


{
  uint32_t earliest_tracked_block_num = _tsp.earliest_tracked_block_num();

  // Have we begun tracking?

#include <hive/plugins/transaction_status_api/transaction_status_api_plugin.hpp>
#include <hive/plugins/transaction_status_api/transaction_status_api.hpp>

#include <hive/plugins/transaction_status/transaction_status_objects.hpp>

namespace hive { namespace plugins { namespace transaction_status_api {

namespace detail {

class transaction_status_api_impl
{
public:
  transaction_status_api_impl() :
    _db( appbase::app().get_plugin< hive::plugins::chain::chain_plugin >().db() ),
    _tsp( appbase::app().get_plugin< hive::plugins::transaction_status::transaction_status_plugin >() ) {}

  DECLARE_API_IMPL( (find_transaction) )

  chain::database& _db;
  transaction_status::transaction_status_plugin& _tsp;
};

DEFINE_API_IMPL( transaction_status_api_impl, find_transaction )

  if ( _db.head_block_num() >= earliest_tracked_block_num )
  {
    auto last_irreversible_block_num = _db.get_last_irreversible_block_num();
    auto tso = _db.find< transaction_status::transaction_status_object, transaction_status::by_trx_id >( args.transaction_id );

    // If we are actively tracking this transaction
    if ( tso != nullptr)
    {
      // If we're not within a block
      if ( !tso->block_num )
        return {
          .status = transaction_status::within_mempool
        };

      // If we're in an irreversible block
      if ( tso->block_num <= last_irreversible_block_num )
        return {
          .status = transaction_status::within_irreversible_block,
          .block_num = tso->block_num
        };
      // We're in a reversible block
      else
        return {
          .status = transaction_status::within_reversible_block,
          .block_num = tso->block_num
        };
    }

    // If the user has provided us with an expiration
    if ( args.expiration.valid() )
    {
      const auto& expiration = *args.expiration;

      // Check if the expiration is before our earliest tracked block plus maximum transaction expiration
      auto earliest_tracked_block = _db.fetch_block_by_number( earliest_tracked_block_num );
      if ( expiration < earliest_tracked_block->timestamp + HIVE_MAX_TIME_UNTIL_EXPIRATION )
        return {
          .status = transaction_status::too_old
        };

      // If the expiration is on or before our last irreversible block
      if ( last_irreversible_block_num > 0 )
      {
        auto last_irreversible_block = _db.fetch_block_by_number( last_irreversible_block_num );
        if ( expiration <= last_irreversible_block->timestamp )
          return {
            .status = transaction_status::expired_irreversible
          };
      }
      // If our expiration is in the past
      if ( expiration < _db.head_block_time() )
        return {
          .status = transaction_status::expired_reversible
        };
    }
  }

  // Either the user did not provide an expiration or it is not expired and we didn't hear about this transaction
  return { .status = transaction_status::unknown };
}

} // hive::plugins::transaction_status_api::detail

transaction_status_api::transaction_status_api() : my( std::make_unique< detail::transaction_status_api_impl >() )
{
  JSON_RPC_REGISTER_API( HIVE_TRANSACTION_STATUS_API_PLUGIN_NAME );
}

transaction_status_api::~transaction_status_api() {}

DEFINE_READ_APIS( transaction_status_api, (find_transaction) )

} } } // hive::plugins::transaction_status_api


if ( _db.head_block_num() >= earliest_tracked_block_num )
  {
    auto last_irreversible_block_num = _db.get_last_irreversible_block_num();
    auto tso = _db.find< transaction_status::transaction_status_object, transaction_status::by_trx_id >( args.transaction_id );

    // If we are actively tracking this transaction
    if ( tso != nullptr)
    {
      // If we're not within a block
      if ( !tso->block_num )
        return {
          .status = transaction_status::within_mempool
        };

      // If we're in an irreversible block
      if ( tso->block_num <= last_irreversible_block_num )
        return {
          .status = transaction_status::within_irreversible_block,
          .block_num = tso->block_num
        };
      // We're in a reversible block
      else
        return {
          .status = transaction_status::within_reversible_block,
          .block_num = tso->block_num
        };
    }

    // If the user has provided us with an expiration
    if ( args.expiration.valid() )
    {
      const auto& expiration = *args.expiration;

      // Check if the expiration is before our earliest tracked block plus maximum transaction expiration
      auto earliest_tracked_block = _db.fetch_block_by_number( earliest_tracked_block_num );
      if ( expiration < earliest_tracked_block->timestamp + HIVE_MAX_TIME_UNTIL_EXPIRATION )
        return {
          .status = transaction_status::too_old
        };

      // If the expiration is on or before our last irreversible block
      if ( last_irreversible_block_num > 0 )
      {
        auto last_irreversible_block = _db.fetch_block_by_number( last_irreversible_block_num );
        if ( expiration <= last_irreversible_block->timestamp )
          return {
            .status = transaction_status::expired_irreversible
          };
      }
      // If our expiration is in the past
      if ( expiration < _db.head_block_time() )
        return {
          .status = transaction_status::expired_reversible
        };
    }
  }

  // Either the user did not provide an expiration or it is not expired and we didn't hear about this transaction

#include <hive/plugins/transaction_status_api/transaction_status_api_plugin.hpp>
#include <hive/plugins/transaction_status_api/transaction_status_api.hpp>

#include <hive/plugins/transaction_status/transaction_status_objects.hpp>

namespace hive { namespace plugins { namespace transaction_status_api {

namespace detail {

class transaction_status_api_impl
{
public:
  transaction_status_api_impl() :
    _db( appbase::app().get_plugin< hive::plugins::chain::chain_plugin >().db() ),
    _tsp( appbase::app().get_plugin< hive::plugins::transaction_status::transaction_status_plugin >() ) {}

  DECLARE_API_IMPL( (find_transaction) )

  chain::database& _db;
  transaction_status::transaction_status_plugin& _tsp;
};

DEFINE_API_IMPL( transaction_status_api_impl, find_transaction )
{
  uint32_t earliest_tracked_block_num = _tsp.earliest_tracked_block_num();

  // Have we begun tracking?

  return { .status = transaction_status::unknown };
}

} // hive::plugins::transaction_status_api::detail

transaction_status_api::transaction_status_api() : my( std::make_unique< detail::transaction_status_api_impl >() )
{
  JSON_RPC_REGISTER_API( HIVE_TRANSACTION_STATUS_API_PLUGIN_NAME );
}

transaction_status_api::~transaction_status_api() {}

DEFINE_READ_APIS( transaction_status_api, (find_transaction) )

} } } // hive::plugins::transaction_status_api


return { .status = transaction_status::unknown };
}

#include <hive/plugins/transaction_status_api/transaction_status_api_plugin.hpp>
#include <hive/plugins/transaction_status_api/transaction_status_api.hpp>

#include <hive/plugins/transaction_status/transaction_status_objects.hpp>

namespace hive { namespace plugins { namespace transaction_status_api {

namespace detail {

class transaction_status_api_impl
{
public:
  transaction_status_api_impl() :
    _db( appbase::app().get_plugin< hive::plugins::chain::chain_plugin >().db() ),
    _tsp( appbase::app().get_plugin< hive::plugins::transaction_status::transaction_status_plugin >() ) {}

  DECLARE_API_IMPL( (find_transaction) )

  chain::database& _db;
  transaction_status::transaction_status_plugin& _tsp;
};

DEFINE_API_IMPL( transaction_status_api_impl, find_transaction )
{
  uint32_t earliest_tracked_block_num = _tsp.earliest_tracked_block_num();

  // Have we begun tracking?
  if ( _db.head_block_num() >= earliest_tracked_block_num )
  {
    auto last_irreversible_block_num = _db.get_last_irreversible_block_num();
    auto tso = _db.find< transaction_status::transaction_status_object, transaction_status::by_trx_id >( args.transaction_id );

    // If we are actively tracking this transaction
    if ( tso != nullptr)
    {
      // If we're not within a block
      if ( !tso->block_num )
        return {
          .status = transaction_status::within_mempool
        };

      // If we're in an irreversible block
      if ( tso->block_num <= last_irreversible_block_num )
        return {
          .status = transaction_status::within_irreversible_block,
          .block_num = tso->block_num
        };
      // We're in a reversible block
      else
        return {
          .status = transaction_status::within_reversible_block,
          .block_num = tso->block_num
        };
    }

    // If the user has provided us with an expiration
    if ( args.expiration.valid() )
    {
      const auto& expiration = *args.expiration;

      // Check if the expiration is before our earliest tracked block plus maximum transaction expiration
      auto earliest_tracked_block = _db.fetch_block_by_number( earliest_tracked_block_num );
      if ( expiration < earliest_tracked_block->timestamp + HIVE_MAX_TIME_UNTIL_EXPIRATION )
        return {
          .status = transaction_status::too_old
        };

      // If the expiration is on or before our last irreversible block
      if ( last_irreversible_block_num > 0 )
      {
        auto last_irreversible_block = _db.fetch_block_by_number( last_irreversible_block_num );
        if ( expiration <= last_irreversible_block->timestamp )
          return {
            .status = transaction_status::expired_irreversible
          };
      }
      // If our expiration is in the past
      if ( expiration < _db.head_block_time() )
        return {
          .status = transaction_status::expired_reversible
        };
    }
  }

  // Either the user did not provide an expiration or it is not expired and we didn't hear about this transaction

} // hive::plugins::transaction_status_api::detail

transaction_status_api::transaction_status_api() : my( std::make_unique< detail::transaction_status_api_impl >() )
{
  JSON_RPC_REGISTER_API( HIVE_TRANSACTION_STATUS_API_PLUGIN_NAME );
}

transaction_status_api::~transaction_status_api() {}

DEFINE_READ_APIS( transaction_status_api, (find_transaction) )

} } } // hive::plugins::transaction_status_api


#include <hive/plugins/transaction_status_api/transaction_status_api_plugin.hpp>
#include <hive/plugins/transaction_status_api/transaction_status_api.hpp>

#include <hive/plugins/transaction_status/transaction_status_objects.hpp>

namespace hive { namespace plugins { namespace transaction_status_api {

namespace detail {

class transaction_status_api_impl
{
public:
  transaction_status_api_impl() :
    _db( appbase::app().get_plugin< hive::plugins::chain::chain_plugin >().db() ),
    _tsp( appbase::app().get_plugin< hive::plugins::transaction_status::transaction_status_plugin >() ) {}

  DECLARE_API_IMPL( (find_transaction) )

  chain::database& _db;
  transaction_status::transaction_status_plugin& _tsp;
};

DEFINE_API_IMPL( transaction_status_api_impl, find_transaction )
{
  uint32_t earliest_tracked_block_num = _tsp.earliest_tracked_block_num();

  // Have we begun tracking?
  if ( _db.head_block_num() >= earliest_tracked_block_num )
  {
    auto last_irreversible_block_num = _db.get_last_irreversible_block_num();
    auto tso = _db.find< transaction_status::transaction_status_object, transaction_status::by_trx_id >( args.transaction_id );

    // If we are actively tracking this transaction
    if ( tso != nullptr)
    {
      // If we're not within a block
      if ( !tso->block_num )
        return {
          .status = transaction_status::within_mempool
        };

      // If we're in an irreversible block
      if ( tso->block_num <= last_irreversible_block_num )
        return {
          .status = transaction_status::within_irreversible_block,
          .block_num = tso->block_num
        };
      // We're in a reversible block
      else
        return {
          .status = transaction_status::within_reversible_block,
          .block_num = tso->block_num
        };
    }

    // If the user has provided us with an expiration
    if ( args.expiration.valid() )
    {
      const auto& expiration = *args.expiration;

      // Check if the expiration is before our earliest tracked block plus maximum transaction expiration
      auto earliest_tracked_block = _db.fetch_block_by_number( earliest_tracked_block_num );
      if ( expiration < earliest_tracked_block->timestamp + HIVE_MAX_TIME_UNTIL_EXPIRATION )
        return {
          .status = transaction_status::too_old
        };

      // If the expiration is on or before our last irreversible block
      if ( last_irreversible_block_num > 0 )
      {
        auto last_irreversible_block = _db.fetch_block_by_number( last_irreversible_block_num );
        if ( expiration <= last_irreversible_block->timestamp )
          return {
            .status = transaction_status::expired_irreversible
          };
      }
      // If our expiration is in the past
      if ( expiration < _db.head_block_time() )
        return {
          .status = transaction_status::expired_reversible
        };
    }
  }

  // Either the user did not provide an expiration or it is not expired and we didn't hear about this transaction
  return { .status =

}

} // hive::plugins::transaction_status_api::detail

transaction_status_api::transaction_status_api() : my( std::make_unique< detail::transaction_status_api_impl >() )
{
  JSON_RPC_REGISTER_API( HIVE_TRANSACTION_STATUS_API_PLUGIN_NAME );
}

transaction_status_api::~transaction_status_api() {}

DEFINE_READ_APIS( transaction_status_api, (find_transaction) )

} } } // hive::plugins::transaction_status_api


transaction_status_api::transaction_status_api() : my( std::make_unique< detail::transaction_status_api_impl >() )
{
  JSON_RPC_REGISTER_API( HIVE_TRANSACTION_STATUS_API_PLUGIN_NAME );
}

#include <hive/plugins/transaction_status_api/transaction_status_api_plugin.hpp>
#include <hive/plugins/transaction_status_api/transaction_status_api.hpp>

#include <hive/plugins/transaction_status/transaction_status_objects.hpp>

namespace hive { namespace plugins { namespace transaction_status_api {

namespace detail {

class transaction_status_api_impl
{
public:
  transaction_status_api_impl() :
    _db( appbase::app().get_plugin< hive::plugins::chain::chain_plugin >().db() ),
    _tsp( appbase::app().get_plugin< hive::plugins::transaction_status::transaction_status_plugin >() ) {}

  DECLARE_API_IMPL( (find_transaction) )

  chain::database& _db;
  transaction_status::transaction_status_plugin& _tsp;
};

DEFINE_API_IMPL( transaction_status_api_impl, find_transaction )
{
  uint32_t earliest_tracked_block_num = _tsp.earliest_tracked_block_num();

  // Have we begun tracking?
  if ( _db.head_block_num() >= earliest_tracked_block_num )
  {
    auto last_irreversible_block_num = _db.get_last_irreversible_block_num();
    auto tso = _db.find< transaction_status::transaction_status_object, transaction_status::by_trx_id >( args.transaction_id );

    // If we are actively tracking this transaction
    if ( tso != nullptr)
    {
      // If we're not within a block
      if ( !tso->block_num )
        return {
          .status = transaction_status::within_mempool
        };

      // If we're in an irreversible block
      if ( tso->block_num <= last_irreversible_block_num )
        return {
          .status = transaction_status::within_irreversible_block,
          .block_num = tso->block_num
        };
      // We're in a reversible block
      else
        return {
          .status = transaction_status::within_reversible_block,
          .block_num = tso->block_num
        };
    }

    // If the user has provided us with an expiration
    if ( args.expiration.valid() )
    {
      const auto& expiration = *args.expiration;

      // Check if the expiration is before our earliest tracked block plus maximum transaction expiration
      auto earliest_tracked_block = _db.fetch_block_by_number( earliest_tracked_block_num );
      if ( expiration < earliest_tracked_block->timestamp + HIVE_MAX_TIME_UNTIL_EXPIRATION )
        return {
          .status = transaction_status::too_old
        };

      // If the expiration is on or before our last irreversible block
      if ( last_irreversible_block_num > 0 )
      {
        auto last_irreversible_block = _db.fetch_block_by_number( last_irreversible_block_num );
        if ( expiration <= last_irreversible_block->timestamp )
          return {
            .status = transaction_status::expired_irreversible
          };
      }
      // If our expiration is in the past
      if ( expiration < _db.head_block_time() )
        return {
          .status = transaction_status::expired_reversible
        };
    }
  }

  // Either the user did not provide an expiration or it is not expired and we didn't hear about this transaction
  return { .status = transaction_status::unknown };
}

} // hive::plugins::transaction_status_api::detail

transaction_status_api::~transaction_status_api() {}

DEFINE_READ_APIS( transaction_status_api, (find_transaction) )

} } } // hive::plugins::transaction_status_api


if ( !tso->block_num )
        return {
          .status = transaction_status::within_mempool
        };

#include <hive/plugins/transaction_status_api/transaction_status_api_plugin.hpp>
#include <hive/plugins/transaction_status_api/transaction_status_api.hpp>

#include <hive/plugins/transaction_status/transaction_status_objects.hpp>

namespace hive { namespace plugins { namespace transaction_status_api {

namespace detail {

class transaction_status_api_impl
{
public:
  transaction_status_api_impl() :
    _db( appbase::app().get_plugin< hive::plugins::chain::chain_plugin >().db() ),
    _tsp( appbase::app().get_plugin< hive::plugins::transaction_status::transaction_status_plugin >() ) {}

  DECLARE_API_IMPL( (find_transaction) )

  chain::database& _db;
  transaction_status::transaction_status_plugin& _tsp;
};

DEFINE_API_IMPL( transaction_status_api_impl, find_transaction )
{
  uint32_t earliest_tracked_block_num = _tsp.earliest_tracked_block_num();

  // Have we begun tracking?
  if ( _db.head_block_num() >= earliest_tracked_block_num )
  {
    auto last_irreversible_block_num = _db.get_last_irreversible_block_num();
    auto tso = _db.find< transaction_status::transaction_status_object, transaction_status::by_trx_id >( args.transaction_id );

    // If we are actively tracking this transaction
    if ( tso != nullptr)
    {
      // If we're not within a block

      // If we're in an irreversible block
      if ( tso->block_num <= last_irreversible_block_num )
        return {
          .status = transaction_status::within_irreversible_block,
          .block_num = tso->block_num
        };
      // We're in a reversible block
      else
        return {
          .status = transaction_status::within_reversible_block,
          .block_num = tso->block_num
        };
    }

    // If the user has provided us with an expiration
    if ( args.expiration.valid() )
    {
      const auto& expiration = *args.expiration;

      // Check if the expiration is before our earliest tracked block plus maximum transaction expiration
      auto earliest_tracked_block = _db.fetch_block_by_number( earliest_tracked_block_num );
      if ( expiration < earliest_tracked_block->timestamp + HIVE_MAX_TIME_UNTIL_EXPIRATION )
        return {
          .status = transaction_status::too_old
        };

      // If the expiration is on or before our last irreversible block
      if ( last_irreversible_block_num > 0 )
      {
        auto last_irreversible_block = _db.fetch_block_by_number( last_irreversible_block_num );
        if ( expiration <= last_irreversible_block->timestamp )
          return {
            .status = transaction_status::expired_irreversible
          };
      }
      // If our expiration is in the past
      if ( expiration < _db.head_block_time() )
        return {
          .status = transaction_status::expired_reversible
        };
    }
  }

  // Either the user did not provide an expiration or it is not expired and we didn't hear about this transaction
  return { .status = transaction_status::unknown };
}

} // hive::plugins::transaction_status_api::detail

transaction_status_api::transaction_status_api() : my( std::make_unique< detail::transaction_status_api_impl >() )
{
  JSON_RPC_REGISTER_API( HIVE_TRANSACTION_STATUS_API_PLUGIN_NAME );
}

transaction_status_api::~transaction_status_api() {}

DEFINE_READ_APIS( transaction_status_api, (find_transaction) )

} } } // hive::plugins::transaction_status_api


// Name:        src/generic/notifmsgg.cpp
// Purpose:     generic implementation of wxGenericNotificationMessage
// Author:      Vadim Zeitlin

// declarations

// headers

// for compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#ifndef wxUSE_LIBHILDON
    #define wxUSE_LIBHILDON 0
#endif

#ifndef wxUSE_LIBHILDON2
    #define wxUSE_LIBHILDON2 0
#endif

#if wxUSE_NOTIFICATION_MESSAGE && (!wxUSE_LIBHILDON || !wxUSE_LIBHILDON2)

#ifndef WX_PRECOMP
    #include "wx/dialog.h"
    #include "wx/timer.h"
    #include "wx/sizer.h"
    #include "wx/statbmp.h"
#endif //WX_PRECOMP

#include "wx/artprov.h"

// even if the platform has the native implementation, we still normally want
// to use the generic one (unless it's totally unsuitable for the target UI as
// is the case of Hildon) because it may provide more features, so include
// wx/generic/notifmsg.h to get wxGenericNotificationMessage declaration even
// if wx/notifmsg.h only declares wxNotificationMessage itself (if it already
// uses the generic version, the second inclusion will do no harm)
#include "wx/notifmsg.h"
#include "wx/generic/notifmsg.h"

// wxNotificationMessageDialog

class wxNotificationMessageDialog : public wxDialog
{
public:
    wxNotificationMessageDialog(wxWindow *parent,
                                const wxString& text,
                                int timeout,
                                int flags);

    void Set(wxWindow *parent,
             const wxString& text,
             int timeout,
             int flags);

    bool IsAutomatic() const { return m_timer.IsRunning(); }
    void SetDeleteOnHide() { m_deleteOnHide = true; }

private:
    void OnClose(wxCloseEvent& event);
    void OnTimer(wxTimerEvent& event);

    // if true, delete the dialog when it should disappear, otherwise just hide
    // it (initially false)
    bool m_deleteOnHide;

    // timer which will hide this dialog when it expires, if it's not running
    // it means we were created without timeout
    wxTimer m_timer;

    DECLARE_EVENT_TABLE()
    wxDECLARE_NO_COPY_CLASS(wxNotificationMessageDialog);
};

// wxNotificationMessageDialog implementation

BEGIN_EVENT_TABLE(wxNotificationMessageDialog, wxDialog)
    EVT_CLOSE(wxNotificationMessageDialog::OnClose)

    EVT_TIMER(wxID_ANY, wxNotificationMessageDialog::OnTimer)
END_EVENT_TABLE()

wxNotificationMessageDialog::wxNotificationMessageDialog(wxWindow *parent,
                                                         const wxString& text,
                                                         int timeout,
                                                         int flags)
                           : wxDialog(parent, wxID_ANY, _("Notice"),
                                      wxDefaultPosition, wxDefaultSize,
                                      0 /* no caption, no border styles */),
                             m_timer(this)
{
    m_deleteOnHide = false;

    Set(parent, text, timeout, flags);
}

void
wxNotificationMessageDialog::Set(wxWindow * WXUNUSED(parent),
                                 const wxString& text,
                                 int timeout,
                                 int flags)
{
    wxSizer * const sizerTop = new wxBoxSizer(wxHORIZONTAL);
    if ( flags & wxICON_MASK )
    {
        sizerTop->Add(new wxStaticBitmap
                          (
                            this,
                            wxID_ANY,
                            wxArtProvider::GetMessageBoxIcon(flags)
                          ),
                      wxSizerFlags().Centre().Border());
    }

    sizerTop->Add(CreateTextSizer(text), wxSizerFlags(1).Border());
    SetSizerAndFit(sizerTop);

    if ( timeout != wxGenericNotificationMessage::Timeout_Never )
    {
        // wxTimer uses ms, timeout is in seconds
        m_timer.Start(timeout*1000, true /* one shot only */);
    }
    else if ( m_timer.IsRunning() )
    {
        m_timer.Stop();
    }
}

void wxNotificationMessageDialog::OnClose(wxCloseEvent& event)
{
    if ( m_deleteOnHide )
    {
        // we don't need to keep this dialog alive any more
        Destroy();
    }
    else // don't really close, just hide, as we can be shown again later
    {
        event.Veto();

        Hide();
    }
}

void wxNotificationMessageDialog::OnTimer(wxTimerEvent& WXUNUSED(event))
{
    if ( m_deleteOnHide )
        Destroy();
    else
        Hide();
}

// wxGenericNotificationMessage implementation

int wxGenericNotificationMessage::ms_timeout = 10;

/* static */

{
    wxASSERT_MSG( timeout > 0,
                "negative or zero default timeout doesn't make sense" );

    ms_timeout = timeout;
}

void wxGenericNotificationMessage::Init()
{
    m_dialog = NULL;
}

wxGenericNotificationMessage::~wxGenericNotificationMessage()
{
    if ( m_dialog->IsAutomatic() )
    {
        // we want to allow the user to create an automatically hidden
        // notification just by creating a local wxGenericNotificationMessage object
        // and so we shouldn't hide the notification when this object goes out
        // of scope
        m_dialog->SetDeleteOnHide();
    }
    else // manual dialog, hide it immediately
    {
        // OTOH for permanently shown dialogs only the code can hide them and
        // if the object is deleted, we must do it now as it won't be
        // accessible programmatically any more
        delete m_dialog;
    }
}

bool wxGenericNotificationMessage::Show(int timeout)
{
    if ( timeout == Timeout_Auto )
    {
        timeout = GetDefaultTimeout();
    }

    if ( !m_dialog )
    {
        m_dialog = new wxNotificationMessageDialog
                       (
                        GetParent(),
                        GetFullMessage(),
                        timeout,
                        GetFlags()
                       );
    }
    else // update the existing dialog
    {
        m_dialog->Set(GetParent(), GetFullMessage(), timeout, GetFlags());
    }

    m_dialog->Show();

    return true;
}

bool wxGenericNotificationMessage::Close()
{
    if ( !m_dialog )
        return false;

    m_dialog->Hide();

    return true;
}

#endif // wxUSE_NOTIFICATION_MESSAGE && (!wxUSE_LIBHILDON || !wxUSE_LIBHILDON2)


{
    if ( m_deleteOnHide )
        Destroy();
    else
        Hide();
}

// Name:        src/generic/notifmsgg.cpp
// Purpose:     generic implementation of wxGenericNotificationMessage
// Author:      Vadim Zeitlin

// declarations

// headers

// for compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#ifndef wxUSE_LIBHILDON
    #define wxUSE_LIBHILDON 0
#endif

#ifndef wxUSE_LIBHILDON2
    #define wxUSE_LIBHILDON2 0
#endif

#if wxUSE_NOTIFICATION_MESSAGE && (!wxUSE_LIBHILDON || !wxUSE_LIBHILDON2)

#ifndef WX_PRECOMP
    #include "wx/dialog.h"
    #include "wx/timer.h"
    #include "wx/sizer.h"
    #include "wx/statbmp.h"
#endif //WX_PRECOMP

#include "wx/artprov.h"

// even if the platform has the native implementation, we still normally want
// to use the generic one (unless it's totally unsuitable for the target UI as
// is the case of Hildon) because it may provide more features, so include
// wx/generic/notifmsg.h to get wxGenericNotificationMessage declaration even
// if wx/notifmsg.h only declares wxNotificationMessage itself (if it already
// uses the generic version, the second inclusion will do no harm)
#include "wx/notifmsg.h"
#include "wx/generic/notifmsg.h"

// wxNotificationMessageDialog

class wxNotificationMessageDialog : public wxDialog
{
public:
    wxNotificationMessageDialog(wxWindow *parent,
                                const wxString& text,
                                int timeout,
                                int flags);

    void Set(wxWindow *parent,
             const wxString& text,
             int timeout,
             int flags);

    bool IsAutomatic() const { return m_timer.IsRunning(); }
    void SetDeleteOnHide() { m_deleteOnHide = true; }

private:
    void OnClose(wxCloseEvent& event);
    void OnTimer(wxTimerEvent& event);

    // if true, delete the dialog when it should disappear, otherwise just hide
    // it (initially false)
    bool m_deleteOnHide;

    // timer which will hide this dialog when it expires, if it's not running
    // it means we were created without timeout
    wxTimer m_timer;

    DECLARE_EVENT_TABLE()
    wxDECLARE_NO_COPY_CLASS(wxNotificationMessageDialog);
};

// wxNotificationMessageDialog implementation

BEGIN_EVENT_TABLE(wxNotificationMessageDialog, wxDialog)
    EVT_CLOSE(wxNotificationMessageDialog::OnClose)

    EVT_TIMER(wxID_ANY, wxNotificationMessageDialog::OnTimer)
END_EVENT_TABLE()

wxNotificationMessageDialog::wxNotificationMessageDialog(wxWindow *parent,
                                                         const wxString& text,
                                                         int timeout,
                                                         int flags)
                           : wxDialog(parent, wxID_ANY, _("Notice"),
                                      wxDefaultPosition, wxDefaultSize,
                                      0 /* no caption, no border styles */),
                             m_timer(this)
{
    m_deleteOnHide = false;

    Set(parent, text, timeout, flags);
}

void
wxNotificationMessageDialog::Set(wxWindow * WXUNUSED(parent),
                                 const wxString& text,
                                 int timeout,
                                 int flags)
{
    wxSizer * const sizerTop = new wxBoxSizer(wxHORIZONTAL);
    if ( flags & wxICON_MASK )
    {
        sizerTop->Add(new wxStaticBitmap
                          (
                            this,
                            wxID_ANY,
                            wxArtProvider::GetMessageBoxIcon(flags)
                          ),
                      wxSizerFlags().Centre().Border());
    }

    sizerTop->Add(CreateTextSizer(text), wxSizerFlags(1).Border());
    SetSizerAndFit(sizerTop);

    if ( timeout != wxGenericNotificationMessage::Timeout_Never )
    {
        // wxTimer uses ms, timeout is in seconds
        m_timer.Start(timeout*1000, true /* one shot only */);
    }
    else if ( m_timer.IsRunning() )
    {
        m_timer.Stop();
    }
}

void wxNotificationMessageDialog::OnClose(wxCloseEvent& event)
{
    if ( m_deleteOnHide )
    {
        // we don't need to keep this dialog alive any more
        Destroy();
    }
    else // don't really close, just hide, as we can be shown again later
    {
        event.Veto();

        Hide();
    }
}

void wxNotificationMessageDialog::OnTimer(wxTimerEvent& WXUNUSED(event))

// wxGenericNotificationMessage implementation

int wxGenericNotificationMessage::ms_timeout = 10;

/* static */ void wxGenericNotificationMessage::SetDefaultTimeout(int timeout)
{
    wxASSERT_MSG( timeout > 0,
                "negative or zero default timeout doesn't make sense" );

    ms_timeout = timeout;
}

void wxGenericNotificationMessage::Init()
{
    m_dialog = NULL;
}

wxGenericNotificationMessage::~wxGenericNotificationMessage()
{
    if ( m_dialog->IsAutomatic() )
    {
        // we want to allow the user to create an automatically hidden
        // notification just by creating a local wxGenericNotificationMessage object
        // and so we shouldn't hide the notification when this object goes out
        // of scope
        m_dialog->SetDeleteOnHide();
    }
    else // manual dialog, hide it immediately
    {
        // OTOH for permanently shown dialogs only the code can hide them and
        // if the object is deleted, we must do it now as it won't be
        // accessible programmatically any more
        delete m_dialog;
    }
}

bool wxGenericNotificationMessage::Show(int timeout)
{
    if ( timeout == Timeout_Auto )
    {
        timeout = GetDefaultTimeout();
    }

    if ( !m_dialog )
    {
        m_dialog = new wxNotificationMessageDialog
                       (
                        GetParent(),
                        GetFullMessage(),
                        timeout,
                        GetFlags()
                       );
    }
    else // update the existing dialog
    {
        m_dialog->Set(GetParent(), GetFullMessage(), timeout, GetFlags());
    }

    m_dialog->Show();

    return true;
}

bool wxGenericNotificationMessage::Close()
{
    if ( !m_dialog )
        return false;

    m_dialog->Hide();

    return true;
}

#endif // wxUSE_NOTIFICATION_MESSAGE && (!wxUSE_LIBHILDON || !wxUSE_LIBHILDON2)