/* === This file is part of Calamares - <https://github.com/calamares> ===
 *
 *   SPDX-FileCopyrightText: 2010-2011 Christian Muehlhaeuser <muesli@tomahawk-player.org>
 *   SPDX-FileCopyrightText: 2014 Teo Mrnjavac <teo@kde.org>
 *   SPDX-FileCopyrightText: 2017 Adriaan de Groot <groot@kde.org>
 *
 *
 *   Calamares 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 3 of the License, or
 *   (at your option) any later version.
 *
 *   Calamares 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 Calamares. If not, see <http://www.gnu.org/licenses/>.
 *
 *   SPDX-License-Identifier: GPL-3.0-or-later
 *   License-Filename: LICENSE
 *
 */

#include "Logger.h"

#include <fstream>
#include <iostream>

#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QMutex>
#include <QTime>
#include <QVariant>

#include "CalamaresVersion.h"
#include "utils/Dirs.h"

#define LOGFILE_SIZE 1024 * 256

static std::ofstream logfile;
static unsigned int s_threshold =
#ifdef QT_NO_DEBUG
    Logger::LOG_DISABLE;
#else
    Logger::LOGEXTRA + 1;  // Comparison is < in log() function
#endif
static QMutex s_mutex;

static const char s_Continuation[] = "\n    ";
static const char s_SubEntry[] = "    .. ";


namespace Logger
{

void
setupLogLevel( unsigned int level )
{
    if ( level > LOGVERBOSE )
    {
        level = LOGVERBOSE;
    }
    s_threshold = level + 1;  // Comparison is < in log() function
}

bool
logLevelEnabled( unsigned int level )
{
    return level < s_threshold;
}

unsigned int
logLevel()
{
    return s_threshold > 0 ? s_threshold - 1 : 0;
}

static void
log( const char* msg, unsigned int debugLevel, bool withTime = true )
{
    if ( true )
    {
        QMutexLocker lock( &s_mutex );

        // If we don't format the date as a Qt::ISODate then we get a crash when
        // logging at exit as Qt tries to use QLocale to format, but QLocale is
        // on its way out.
        logfile << QDate::currentDate().toString( Qt::ISODate ).toUtf8().data() << " - "
                << QTime::currentTime().toString().toUtf8().data() << " ["
                << QString::number( debugLevel ).toUtf8().data() << "]: " << msg << std::endl;

        logfile.flush();
    }

    if ( logLevelEnabled( debugLevel ) )
    {
        QMutexLocker lock( &s_mutex );
        if ( withTime )
        {
            std::cout << QTime::currentTime().toString().toUtf8().data() << " ["
                      << QString::number( debugLevel ).toUtf8().data() << "]: ";
        }
        std::cout << msg << std::endl;
    }
}


static void
CalamaresLogHandler( QtMsgType type, const QMessageLogContext&, const QString& msg )
{
    static QMutex s_mutex;

    QByteArray ba = msg.toUtf8();
    const char* message = ba.constData();

    QMutexLocker locker( &s_mutex );
    switch ( type )
    {
    case QtDebugMsg:
        log( message, LOGVERBOSE );
        break;

    case QtInfoMsg:
        log( message, 1 );
        break;

    case QtCriticalMsg:
    case QtWarningMsg:
    case QtFatalMsg:
        log( message, 0 );
        break;
    }
}


QString
logFile()
{
    return CalamaresUtils::appLogDir().filePath( "session.log" );
}


void
setupLogfile()
{
    if ( QFileInfo( logFile().toLocal8Bit() ).size() > LOGFILE_SIZE )
    {
        QByteArray lc;
        {
            QFile f( logFile().toLocal8Bit() );
            f.open( QIODevice::ReadOnly | QIODevice::Text );
            lc = f.readAll();
            f.close();
        }

        QFile::remove( logFile().toLocal8Bit() );

        {
            QFile f( logFile().toLocal8Bit() );
            f.open( QIODevice::WriteOnly | QIODevice::Text );
            f.write( lc.right( LOGFILE_SIZE - ( LOGFILE_SIZE / 4 ) ) );
            f.close();
        }
    }

    // Since the log isn't open yet, this probably only goes to stdout
    cDebug() << "Using log file:" << logFile();

    // Lock while (re-)opening the logfile
    {
        QMutexLocker lock( &s_mutex );
        logfile.open( logFile().toLocal8Bit(), std::ios::app );
        if ( logfile.tellp() )
        {
            logfile << "\n\n" << std::endl;
        }
        logfile << "=== START CALAMARES " << CALAMARES_VERSION << std::endl;
    }

    qInstallMessageHandler( CalamaresLogHandler );
}

CDebug::CDebug( unsigned int debugLevel, const char* func )
    : QDebug( &m_msg )
    , m_debugLevel( debugLevel )
    , m_funcinfo( func )
{
    if ( debugLevel <= LOGERROR )
    {
        m_msg = QStringLiteral( "ERROR:" );
    }
    else if ( debugLevel <= LOGWARNING )
    {
        m_msg = QStringLiteral( "WARNING:" );
    }
}


CDebug::~CDebug()
{
    if ( logLevelEnabled( m_debugLevel ) )
    {
        if ( m_funcinfo )
        {
            m_msg.prepend( s_Continuation );  // Prepending, so back-to-front
            m_msg.prepend( m_funcinfo );
        }
        log( m_msg.toUtf8().data(), m_debugLevel, m_funcinfo );
    }
}

constexpr FuncSuppressor::FuncSuppressor( const char s[] )
    : m_s( s )
{
}

const constexpr FuncSuppressor Continuation( s_Continuation );
const constexpr FuncSuppressor SubEntry( s_SubEntry );

QString
toString( const QVariant& v )
{
    auto t = v.type();

    if ( t == QVariant::List )
    {
        QStringList s;
        auto l = v.toList();
        for ( auto lit = l.constBegin(); lit != l.constEnd(); ++lit )
        {
            s << lit->toString();
        }
        return s.join( ", " );
    }
    else
    {
        return v.toString();
    }
}

}  // namespace Logger