/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "FileWatcher.h"
#include <AzCore/Debug/Trace.h>
#include <native/assetprocessor.h>

//////////////////////////////////////////////////////////////////////////////
/// FolderWatchRoot
void FolderRootWatch::ProcessNewFileEvent(const QString& file)
{
    FileChangeInfo info;
    info.m_action = FileAction::FileAction_Added;
    info.m_filePath = file;
    const bool invoked = QMetaObject::invokeMethod(m_fileWatcher, "AnyFileChange", Qt::QueuedConnection, Q_ARG(FileChangeInfo, info));
    Q_ASSERT(invoked);
}

void FolderRootWatch::ProcessDeleteFileEvent(const QString& file)
{
    FileChangeInfo info;
    info.m_action = FileAction::FileAction_Removed;
    info.m_filePath = file;
    const bool invoked = QMetaObject::invokeMethod(m_fileWatcher, "AnyFileChange", Qt::QueuedConnection, Q_ARG(FileChangeInfo, info));
    Q_ASSERT(invoked);
}

void FolderRootWatch::ProcessModifyFileEvent(const QString& file)
{
    FileChangeInfo info;
    info.m_action = FileAction::FileAction_Modified;
    info.m_filePath = file;
    const bool invoked = QMetaObject::invokeMethod(m_fileWatcher, "AnyFileChange", Qt::QueuedConnection, Q_ARG(FileChangeInfo, info));
    Q_ASSERT(invoked);
}

//////////////////////////////////////////////////////////////////////////
/// FileWatcher
FileWatcher::FileWatcher()
    : m_nextHandle(0)
{
    qRegisterMetaType<FileChangeInfo>("FileChangeInfo");
}

FileWatcher::~FileWatcher()
{
}

int FileWatcher::AddFolderWatch(FolderWatchBase* pFolderWatch)
{
    if (!pFolderWatch)
    {
        return -1;
    }

    FolderRootWatch* pFolderRootWatch = nullptr;

    //see if this a sub folder of an already watched root
    for (auto rootsIter = m_folderWatchRoots.begin(); !pFolderRootWatch && rootsIter != m_folderWatchRoots.end(); ++rootsIter)
    {
        if (FolderWatchBase::IsSubfolder(pFolderWatch->m_folder, (*rootsIter)->m_root))
        {
            pFolderRootWatch = *rootsIter;
        }
    }

    bool bCreatedNewRoot = false;
    //if its not a sub folder
    if (!pFolderRootWatch)
    {
        //create a new root and start listening for changes
        pFolderRootWatch = new FolderRootWatch(pFolderWatch->m_folder);

        //make sure the folder watcher(s) get deleted before this
        pFolderRootWatch->setParent(this);
        bCreatedNewRoot = true;
    }

    pFolderRootWatch->m_fileWatcher = this;
    QObject::connect(this, &FileWatcher::AnyFileChange, pFolderWatch, &FolderWatchBase::OnAnyFileChange);

    if (bCreatedNewRoot)
    {
        if (m_startedWatching)
        {
            pFolderRootWatch->Start();
        }

        //since we created a new root, see if the new root is a super folder
        //of other roots, if it is then then fold those roots into the new super root
        for (auto rootsIter = m_folderWatchRoots.begin(); rootsIter != m_folderWatchRoots.end(); )
        {
            if (FolderWatchBase::IsSubfolder((*rootsIter)->m_root, pFolderWatch->m_folder))
            {
                //union the sub folder map over to the new root
                pFolderRootWatch->m_subFolderWatchesMap.insert((*rootsIter)->m_subFolderWatchesMap);

                //clear the old root sub folders map so they don't get deleted when we
                //delete the old root as they are now pointed to by the new root
                (*rootsIter)->m_subFolderWatchesMap.clear();

                //delete the empty old root, deleting a root will call Stop()
                //automatically which kills the thread
                delete *rootsIter;

                //remove the old root pointer form the watched list
                rootsIter = m_folderWatchRoots.erase(rootsIter);
            }
            else
            {
                ++rootsIter;
            }
        }

        //add the new root to the watched roots
        m_folderWatchRoots.push_back(pFolderRootWatch);
    }

    //add to the root
    pFolderRootWatch->m_subFolderWatchesMap.insert(m_nextHandle, pFolderWatch);

    m_nextHandle++;

    return m_nextHandle - 1;
}

void FileWatcher::RemoveFolderWatch(int handle)
{
    for (auto rootsIter = m_folderWatchRoots.begin(); rootsIter != m_folderWatchRoots.end(); )
    {
        //find an element by the handle
        auto foundIter = (*rootsIter)->m_subFolderWatchesMap.find(handle);
        if (foundIter != (*rootsIter)->m_subFolderWatchesMap.end())
        {
            //remove the element
            (*rootsIter)->m_subFolderWatchesMap.erase(foundIter);

            //we removed a folder watch, if it's empty then there is no reason to keep watching it.
            if ((*rootsIter)->m_subFolderWatchesMap.empty())
            {
                delete(*rootsIter);
                rootsIter = m_folderWatchRoots.erase(rootsIter);
            }
            else
            {
                ++rootsIter;
            }
        }
        else
        {
            ++rootsIter;
        }
    }
}

void FileWatcher::StartWatching()
{
    if (m_startedWatching)
    {
        AZ_Warning("FileWatcher", false, "StartWatching() called when already watching for file changes.");
        return;
    }

    for (FolderRootWatch* root : m_folderWatchRoots)
    {
        root->Start();
    }

    AZ_TracePrintf(AssetProcessor::ConsoleChannel, "File Change Monitoring started.\n");
    m_startedWatching = true;
}

void FileWatcher::StopWatching()
{
    if (!m_startedWatching)
    {
        AZ_Warning("FileWatcher", false, "StartWatching() called when is not watching for file changes.");
        return;
    }

    for (FolderRootWatch* root : m_folderWatchRoots)
    {
        root->Stop();
    }

    m_startedWatching = false;
}

#include <native/FileWatcher/FileWatcher.moc>
#include <native/FileWatcher/FileWatcherAPI.moc>