/* Copyright (C) 2017-2021 J.F.Dockes
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
#include "autoconfig.h"

#include <mutex>

#include "chrono.h"
#include "conftree.h"
#include "idxstatus.h"
#include "log.h"
#include "rclconfig.h"
#include "x11mon.h"

using std::string;

// Global stop request flag. This is checked in a number of place in the
// indexing routines.
int stopindexing;

void readIdxStatus(RclConfig *config, DbIxStatus &status)
{
    ConfSimple cs(config->getIdxStatusFile().c_str(), 1);
    status.phase = DbIxStatus::Phase(cs.getInt("phase", 0));
    cs.get("fn", status.fn);
    status.docsdone = (int)cs.getInt("docsdone", 0);
    status.filesdone = (int)cs.getInt("filesdone", 0);
    status.fileerrors = (int)cs.getInt("fileerrors", 0);
    status.dbtotdocs = (int)cs.getInt("dbtotdocs", 0);
    status.totfiles = (int)cs.getInt("totfiles", 0);
    status.hasmonitor = cs.getBool("hasmonitor", false);
}

// Receive status updates from the ongoing indexing operation
// Also check for an interrupt request and return the info to caller which
// should subsequently orderly terminate what it is doing.
class DbIxStatusUpdater::Internal {
public:
#ifdef IDX_THREADS
    std::mutex m_mutex;
#endif
    Internal(const RclConfig *config, bool nox11mon)
        : m_file(config->getIdxStatusFile().c_str()), m_stopfilename(config->getIdxStopFile()),
          nox11monitor(nox11mon) {
        // The total number of files included in the index is actually
        // difficult to compute from the index itself. For display
        // purposes, we save it in the status file from indexing to
        // indexing (mostly...)
        string stf;
        if (m_file.get("totfiles", stf)) {
            status.totfiles = atoi(stf.c_str());
        }
    }
    virtual ~Internal() = default;
    
    virtual bool update() {
        if (status.dbtotdocs < status.docsdone)
            status.dbtotdocs = status.docsdone;
        // Update the status file. Avoid doing it too often. Always do
        // it at the end (status DONE)
        if (status.phase == DbIxStatus::DBIXS_DONE || 
            status.phase != m_prevphase || m_chron.millis() > 300) {
            if (status.totfiles < status.filesdone || status.phase == DbIxStatus::DBIXS_DONE) {
                status.totfiles = status.filesdone;
            }
            m_prevphase = status.phase;
            m_chron.restart();
            if (status != prevstatus) {
                m_file.holdWrites(true);
                m_file.set("phase", int(status.phase));
                m_file.set("docsdone", status.docsdone);
                m_file.set("filesdone", status.filesdone);
                m_file.set("fileerrors", status.fileerrors);
                m_file.set("dbtotdocs", status.dbtotdocs);
                m_file.set("totfiles", status.totfiles);
                m_file.set("fn", status.fn);
                m_file.set("hasmonitor", status.hasmonitor);
                m_file.holdWrites(false);
                prevstatus = status;
            }
        }
        if (path_exists(m_stopfilename)) {
            LOGINF("recollindex: asking indexer to stop because " << m_stopfilename << " exists\n");
            path_unlink(m_stopfilename);
            stopindexing = true;
        }
        if (stopindexing) {
            return false;
        }

#ifndef DISABLE_X11MON
        // If we are in the monitor, we also need to check X11 status
        // during the initial indexing pass (else the user could log
        // out and the indexing would go on, not good (ie: if the user
        // logs in again, the new recollindex will fail).
        if (status.hasmonitor && !nox11monitor && !x11IsAlive()) {
            LOGDEB("X11 session went away during initial indexing pass\n");
            stopindexing = true;
            return false;
        }
#endif
        return true;
    }

    DbIxStatus status;
    DbIxStatus prevstatus;
    ConfSimple m_file;
    string m_stopfilename;
    Chrono m_chron;
    bool nox11monitor{false};
    DbIxStatus::Phase m_prevphase{DbIxStatus::DBIXS_NONE};
};


DbIxStatusUpdater::DbIxStatusUpdater(const RclConfig *config, bool nox11monitor) {
    m = new Internal(config, nox11monitor);
}

void DbIxStatusUpdater::setMonitor(bool onoff)
{
    m->status.hasmonitor = onoff;
}

void DbIxStatusUpdater::setDbTotDocs(int totdocs)
{
#ifdef IDX_THREADS
    std::unique_lock<std::mutex>  lock(m->m_mutex);
#endif
    m->status.dbtotdocs = totdocs;
}

bool DbIxStatusUpdater::update(DbIxStatus::Phase phase, const string& fn, int incr) {
#ifdef IDX_THREADS
    std::unique_lock<std::mutex>  lock(m->m_mutex);
#endif

    // We don't change a FLUSH status except if the new status is NONE
    // (recollindex init or rcldb after commit(). Else, the flush status maybe
    // overwritten by a "file updated" status and not be displayed
    if (phase == DbIxStatus::DBIXS_NONE || m->status.phase != DbIxStatus::DBIXS_FLUSH)
        m->status.phase = phase;
    m->status.fn = fn;
    if (incr & IncrDocsDone)
        m->status.docsdone++;
    if (incr & IncrFilesDone)
        m->status.filesdone++;
    if (incr & IncrFileErrors)
        m->status.fileerrors++;
    return m->update();
}

static DbIxStatusUpdater *updater;

DbIxStatusUpdater *statusUpdater(RclConfig *config, bool nox11mon)
{
    if (updater) {
        return updater;
    }
    return (updater = new DbIxStatusUpdater(config, nox11mon));
}
