//=============================================================================
//  MuseScore
//  Music Composition & Notation
//
//  Copyright (C) 2020 MuseScore BVBA and others
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#include "workspacemanager.h"

#include "log.h"

using namespace mu;
using namespace mu::workspace;
using namespace mu::extensions;

static bool containsWorkspace(const IWorkspacePtrList& list, const IWorkspacePtr& workspace)
{
    return std::find(list.cbegin(), list.cend(), workspace) != list.cend();
}

RetValCh<IWorkspacePtr> WorkspaceManager::currentWorkspace() const
{
    RetValCh<IWorkspacePtr> rv;

    if (!m_currentWorkspace) {
        rv.ret = make_ret(Ret::Code::UnknownError);
        rv.val = nullptr;
    } else {
        rv.ret = make_ret(Ret::Code::Ok);
        rv.val = m_currentWorkspace;
    }

    rv.ch = m_currentWorkspaceChanged;

    return rv;
}

RetVal<IWorkspacePtrList> WorkspaceManager::workspaces() const
{
    RetVal<IWorkspacePtrList> result;
    result.ret = make_ret(Ret::Code::Ok);

    for (auto workspace : m_workspaces) {
        result.val.push_back(workspace);
    }

    return result;
}

Ret WorkspaceManager::setWorkspaces(const IWorkspacePtrList& workspaces)
{
    Ret ret = removeMissingWorkspaces(workspaces);

    if (ret) {
        ret = createInexistentWorkspaces(workspaces);
    }

    return ret;
}

Ret WorkspaceManager::removeMissingWorkspaces(const IWorkspacePtrList& newWorkspaceList)
{
    RetVal<IWorkspacePtrList> oldWorkspaceList = workspaces();
    if (!oldWorkspaceList.ret) {
        return oldWorkspaceList.ret;
    }

    for (const IWorkspacePtr& oldWorkspace : oldWorkspaceList.val) {
        if (containsWorkspace(newWorkspaceList, oldWorkspace)) {
            continue;
        }

        Ret ret = removeWorkspace(oldWorkspace);
        if (!ret) {
            return ret;
        }
    }

    return make_ret(Ret::Code::Ok);
}

Ret WorkspaceManager::removeWorkspace(const IWorkspacePtr& workspace)
{
    std::string workspaceName = workspace->name();
    if (!canRemoveWorkspace(workspaceName)) {
        return make_ret(Ret::Code::Ok);
    }

    for (auto it = m_workspaces.begin(); it != m_workspaces.end(); ++it) {
        if (it->get()->name() == workspaceName) {
            m_workspaces.erase(it);
            return fileSystem()->remove(it->get()->filePath());
        }
    }

    return make_ret(Ret::Code::Ok);
}

bool WorkspaceManager::canRemoveWorkspace(const std::string& workspaceName) const
{
    return workspaceName != DEFAULT_WORKSPACE_NAME;
}

Ret WorkspaceManager::createInexistentWorkspaces(const IWorkspacePtrList& newWorkspaceList)
{
    RetVal<IWorkspacePtrList> existentWorkspaces = workspaces();
    if (!existentWorkspaces.ret) {
        return existentWorkspaces.ret;
    }

    for (const IWorkspacePtr& workspace : newWorkspaceList) {
        if (containsWorkspace(existentWorkspaces.val, workspace)) {
            continue;
        }

        Ret ret = createWorkspace(workspace);
        if (!ret) {
            return ret;
        }
    }

    return make_ret(Ret::Code::Ok);
}

Ret WorkspaceManager::createWorkspace(IWorkspacePtr workspace)
{
    auto writable = std::dynamic_pointer_cast<Workspace>(workspace);
    if (!writable) {
        return make_ret(Ret::Code::Ok);
    }

    Ret ret = writable->write();

    if (ret) {
        m_workspaces.push_back(writable);
    }

    return ret;
}

void WorkspaceManager::init()
{
    Ret ret = fileSystem()->makePath(configuration()->userWorkspacesDirPath());
    if (!ret) {
        LOGE() << ret.toString();
    }

    RetCh<Extension> extensionChanged = extensionsService()->extensionChanged();
    if (extensionChanged.ret) {
        extensionChanged.ch.onReceive(this, [this](const Extension& newExtension) {
            if (newExtension.types.testFlag(Extension::Workspaces)) {
                load();
            }
        });
    }

    configuration()->currentWorkspaceName().ch.onReceive(this, [this](const std::string&) {
        setupCurrentWorkspace();
    });

    load();
}

void WorkspaceManager::load()
{
    m_workspaces.clear();

    io::paths files = findWorkspaceFiles();

    for (const io::path& file : files) {
        auto workspace = std::make_shared<Workspace>(file);
        m_workspaces.push_back(workspace);
    }

    setupCurrentWorkspace();
}

io::paths WorkspaceManager::findWorkspaceFiles() const
{
    io::paths result;
    io::paths dirPaths = configuration()->workspacePaths();

    for (const io::path& dirPath : dirPaths) {
        RetVal<io::paths> files = fileSystem()->scanFiles(dirPath, { "*.workspace" });
        if (!files.ret) {
            LOGE() << files.ret.toString();
            continue;
        }

        result.insert(result.end(), files.val.begin(), files.val.end());
    }

    return result;
}

void WorkspaceManager::setupCurrentWorkspace()
{
    saveCurrentWorkspace();

    std::string workspaceName = configuration()->currentWorkspaceName().val;

    WorkspacePtr workspace = findAndInit(workspaceName);
    if (!workspace) {
        std::string defaultWorkspaceName(DEFAULT_WORKSPACE_NAME);
        LOGW() << "failed get workspace: " << workspaceName << ", will use " << defaultWorkspaceName;
        workspace = findAndInit(defaultWorkspaceName);
    }

    if (workspace) {
        m_currentWorkspace = workspace;
        m_currentWorkspaceChanged.send(workspace);
    }
}

WorkspacePtr WorkspaceManager::findByName(const std::string& name) const
{
    for (auto workspace : m_workspaces) {
        if (workspace->name() == name) {
            return workspace;
        }
    }

    return nullptr;
}

WorkspacePtr WorkspaceManager::findAndInit(const std::string& name) const
{
    WorkspacePtr workspace = findByName(name);
    if (!workspace) {
        return nullptr;
    }

    if (!workspace->isInited()) {
        Ret ret = workspace->read();
        if (!ret) {
            LOGE() << "failed read workspace: " << name;
            return nullptr;
        }
    }

    return workspace;
}

void WorkspaceManager::deinit()
{
    saveCurrentWorkspace();
}

void WorkspaceManager::saveCurrentWorkspace()
{
    if (!m_currentWorkspace) {
        return;
    }

    Ret ret = m_currentWorkspace->write();
    if (!ret) {
        LOGE() << ret.toString();
    }
}
