/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "gfxFontInfoLoader.h"
#include "nsCRT.h"
#include "nsIObserverService.h"
#include "nsThreadUtils.h"              // for nsRunnable
#include "gfxPlatformFontList.h"

using namespace mozilla;
using services::GetObserverService;

#define LOG_FONTINIT(args) MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), \
                               LogLevel::Debug, args)
#define LOG_FONTINIT_ENABLED() MOZ_LOG_TEST( \
                                   gfxPlatform::GetLog(eGfxLog_fontinit), \
                                   LogLevel::Debug)

void
FontInfoData::Load()
{
    TimeStamp start = TimeStamp::Now();

    uint32_t i, n = mFontFamiliesToLoad.Length();
    mLoadStats.families = n;
    for (i = 0; i < n && !mCanceled; i++) {
        // font file memory mapping sometimes causes exceptions - bug 1100949
        MOZ_SEH_TRY {
            LoadFontFamilyData(mFontFamiliesToLoad[i]);
        } MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
            gfxCriticalError() <<
                "Exception occurred reading font data for " <<
                NS_ConvertUTF16toUTF8(mFontFamiliesToLoad[i]).get();
        }
    }

    mLoadTime = TimeStamp::Now() - start;
}

class FontInfoLoadCompleteEvent : public Runnable {
    virtual ~FontInfoLoadCompleteEvent() {}

    NS_DECL_ISUPPORTS_INHERITED

    explicit FontInfoLoadCompleteEvent(FontInfoData *aFontInfo) :
        mFontInfo(aFontInfo)
    {}

    NS_IMETHOD Run() override;

    RefPtr<FontInfoData> mFontInfo;
};

class AsyncFontInfoLoader : public Runnable {
    virtual ~AsyncFontInfoLoader() {}

    NS_DECL_ISUPPORTS_INHERITED

    explicit AsyncFontInfoLoader(FontInfoData *aFontInfo) :
        mFontInfo(aFontInfo)
    {
        mCompleteEvent = new FontInfoLoadCompleteEvent(aFontInfo);
    }

    NS_IMETHOD Run() override;

    RefPtr<FontInfoData> mFontInfo;
    RefPtr<FontInfoLoadCompleteEvent> mCompleteEvent;
};

class ShutdownThreadEvent : public Runnable {
    virtual ~ShutdownThreadEvent() {}

    NS_DECL_ISUPPORTS_INHERITED

    explicit ShutdownThreadEvent(nsIThread* aThread) : mThread(aThread) {}
    NS_IMETHOD Run() override {
        mThread->Shutdown();
        return NS_OK;
    }
    nsCOMPtr<nsIThread> mThread;
};

NS_IMPL_ISUPPORTS_INHERITED0(ShutdownThreadEvent, Runnable);

// runs on main thread after async font info loading is done
nsresult
FontInfoLoadCompleteEvent::Run()
{
    gfxFontInfoLoader *loader =
        static_cast<gfxFontInfoLoader*>(gfxPlatformFontList::PlatformFontList());

    loader->FinalizeLoader(mFontInfo);

    return NS_OK;
}

NS_IMPL_ISUPPORTS_INHERITED0(FontInfoLoadCompleteEvent, Runnable);

// runs on separate thread
nsresult
AsyncFontInfoLoader::Run()
{
    // load platform-specific font info
    mFontInfo->Load();

    // post a completion event that transfer the data to the fontlist
    NS_DispatchToMainThread(mCompleteEvent);

    return NS_OK;
}

NS_IMPL_ISUPPORTS_INHERITED0(AsyncFontInfoLoader, Runnable);

NS_IMPL_ISUPPORTS(gfxFontInfoLoader::ShutdownObserver, nsIObserver)

NS_IMETHODIMP
gfxFontInfoLoader::ShutdownObserver::Observe(nsISupports *aSubject,
                                             const char *aTopic,
                                             const char16_t *someData)
{
    if (!nsCRT::strcmp(aTopic, "quit-application")) {
        mLoader->CancelLoader();
    } else {
        NS_NOTREACHED("unexpected notification topic");
    }
    return NS_OK;
}

void
gfxFontInfoLoader::StartLoader(uint32_t aDelay, uint32_t aInterval)
{
    mInterval = aInterval;

    NS_ASSERTION(!mFontInfo,
                 "fontinfo should be null when starting font loader");

    // sanity check
    if (mState != stateInitial &&
        mState != stateTimerOff &&
        mState != stateTimerOnDelay) {
        CancelLoader();
    }

    // set up timer
    if (!mTimer) {
        mTimer = do_CreateInstance("@mozilla.org/timer;1");
        if (!mTimer) {
            NS_WARNING("Failure to create font info loader timer");
            return;
        }
    }

    AddShutdownObserver();

    // delay? ==> start async thread after a delay
    if (aDelay) {
        mState = stateTimerOnDelay;
        mTimer->InitWithFuncCallback(DelayedStartCallback, this, aDelay,
                                     nsITimer::TYPE_ONE_SHOT);
        return;
    }

    mFontInfo = CreateFontInfoData();

    // initialize
    InitLoader();

    // start async load
    nsresult rv = NS_NewNamedThread("Font Loader",
                                    getter_AddRefs(mFontLoaderThread),
                                    nullptr);
    if (NS_WARN_IF(NS_FAILED(rv))) {
        return;
    }
    mState = stateAsyncLoad;

    nsCOMPtr<nsIRunnable> loadEvent = new AsyncFontInfoLoader(mFontInfo);

    mFontLoaderThread->Dispatch(loadEvent.forget(), NS_DISPATCH_NORMAL);

    if (LOG_FONTINIT_ENABLED()) {
        LOG_FONTINIT(("(fontinit) fontloader started (fontinfo: %p)\n",
                      mFontInfo.get()));
    }
}

void
gfxFontInfoLoader::FinalizeLoader(FontInfoData *aFontInfo)
{
    // Avoid loading data if loader has already been canceled.
    // This should mean that CancelLoader() ran and the Load
    // thread has already Shutdown(), and likely before processing
    // the Shutdown event it handled the load event and sent back
    // our Completion event, thus we end up here.
    if (mState != stateAsyncLoad || mFontInfo != aFontInfo) {
        return;
    }

    mLoadTime = mFontInfo->mLoadTime;

    // try to load all font data immediately
    if (LoadFontInfo()) {
        CancelLoader();
        return;
    }

    // not all work completed ==> run load on interval
    mState = stateTimerOnInterval;
    mTimer->InitWithFuncCallback(LoadFontInfoCallback, this, mInterval,
                                 nsITimer::TYPE_REPEATING_SLACK);
}

void
gfxFontInfoLoader::CancelLoader()
{
    if (mState == stateInitial) {
        return;
    }
    mState = stateTimerOff;
    if (mTimer) {
        mTimer->Cancel();
        mTimer = nullptr;
    }
    if (mFontInfo) // null during any initial delay
        mFontInfo->mCanceled = true;
    if (mFontLoaderThread) {
        NS_DispatchToMainThread(new ShutdownThreadEvent(mFontLoaderThread));
        mFontLoaderThread = nullptr;
    }
    RemoveShutdownObserver();
    CleanupLoader();
}

void
gfxFontInfoLoader::LoadFontInfoTimerFire()
{
    if (mState == stateTimerOnDelay) {
        mState = stateTimerOnInterval;
        mTimer->SetDelay(mInterval);
    }

    bool done = LoadFontInfo();
    if (done) {
        CancelLoader();
    }
}

gfxFontInfoLoader::~gfxFontInfoLoader()
{
    RemoveShutdownObserver();
    MOZ_COUNT_DTOR(gfxFontInfoLoader);
}

void
gfxFontInfoLoader::AddShutdownObserver()
{
    if (mObserver) {
        return;
    }

    nsCOMPtr<nsIObserverService> obs = GetObserverService();
    if (obs) {
        mObserver = new ShutdownObserver(this);
        obs->AddObserver(mObserver, "quit-application", false);
    }
}

void
gfxFontInfoLoader::RemoveShutdownObserver()
{
    if (mObserver) {
        nsCOMPtr<nsIObserverService> obs = GetObserverService();
        if (obs) {
            obs->RemoveObserver(mObserver, "quit-application");
            mObserver = nullptr;
        }
    }
}