// 
//   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
//   Free Software Foundation, Inc
// 
// 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 3 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 St, Fifth Floor, Boston, MA  02110-1301  USA

#ifndef GNASH_LOG_H
#define GNASH_LOG_H

#ifdef HAVE_CONFIG_H
#include "gnashconfig.h"
#endif

#include "rc.h" // for IF_VERBOSE_* implementation
#include "dsodefs.h" // for DSOEXPORT

#include <fstream>
#include <mutex>
#include <boost/format.hpp>

// This is needed so we can print to the Android log file, which can
// be retrieved with logcat.
#ifdef __ANDROID__
#include <android/log.h>
#endif

// the default name for the debug log
#define DEFAULT_LOGFILE "gnash-dbg.log"

// Support compilation with (or without) native language support
#include "gettext.h"
#define    _(String) gettext (String)
#define N_(String) gettext_noop (String)

// Macro to prevent repeated logging calls for the same
// event
#define LOG_ONCE(x) { \
    static bool warned = false; \
    if (!warned) { warned = true; x; } \
}

// Mingw32 (win32 console) doesn't use the standard GCC defines that
// Gnash used for debug messages, so make it so...
#ifndef __FUNCDNAME__
#define __FUNCDNAME__ __FUNCTION__
#endif

namespace gnash {

// This is a basic file logging class
class DSOEXPORT LogFile
{
public:

    static LogFile& getDefaultInstance();

    ~LogFile();

    enum LogLevel {
        LOG_SILENT,
        LOG_NORMAL,
        LOG_DEBUG,
        LOG_EXTRA
    };

    enum FileState {
        CLOSED,
        OPEN,
        INPROGRESS,
        IDLE
    };

    /// Intended for use by log_*(). Thread-safe (locks _ioMutex)
    //
    /// @param label
    ///        The label string ie: "ERROR" for "ERROR: <msg>"
    ///
    /// @param msg
    ///        The message string ie: "bah" for "ERROR: bah"
    ///
    void log(const std::string& label, const std::string& msg);

    /// Intended for use by log_*(). Thread-safe (locks _ioMutex)
    //
    /// @param msg
    ///        The message to print
    ///
    void log(const std::string& msg);
    
    /// Remove the log file
    //
    /// Does NOT lock _ioMutex (should it?)
    ///
    bool removeLog();

    /// Close the log file
    //
    /// Locks _ioMutex to prevent race conditions accessing _outstream
    ///
    bool closeLog();

    /// Set log filename 
    //
    /// If a log file is opened already, it will be closed
    /// by this call, and will be reopened on next use
    /// if needed.
    ///
    void setLogFilename(const std::string& fname);

    // accessors for the verbose level
    void setVerbosity() {
        ++_verbose;
    }

    void setVerbosity(int x) {
        _verbose = x;
    }

    int getVerbosity() const {
        return _verbose;
    }
    
    void setActionDump(int x) {
        _actiondump = x;
    }

    void setNetwork(int x) {
        _network = x;
    }

    int getActionDump() const {
        return _actiondump;
    }
    
    int getNetwork() const {
        return _network;
    }
    
    void setParserDump (int x) {
        _parserdump = x;
    }

    int getParserDump() const {
        return _parserdump;
    }
    
    void setStamp (bool b) {
        _stamp = b;
    }

    bool getStamp() const {
        return _stamp;
    }

    /// Set whether to write logs to file
    void setWriteDisk(bool b);

    bool getWriteDisk() const {
        return _write;
    }
    
    typedef void (*logListener)(const std::string& s);
    
    void registerLogCallback(logListener l) { _listener = l; }

private:
    
    /// Open the specified file to write logs on disk
    //
    /// Locks _ioMutex to prevent race conditions accessing _outstream
    ///
    /// @return true on success, false on failure
    ///
    bool openLog(const std::string& filespec);

    /// \brief
    /// Open the RcInitFile-specified log file if log write
    /// is requested. 
    //
    /// This method is called before any attempt to write is made.
    /// It will return true if the file was opened, false if wasn't
    /// (either not requested or error).
    ///
    /// On error, will print a message on stderr
    ///
    bool openLogIfNeeded();

    // Use getDefaultInstance for getting the singleton
    LogFile ();

    /// Mutex for locking I/O during logfile access.
    std::mutex _ioMutex;

    /// Stream to write to stdout.
    std::ofstream _outstream;

    /// How much output is required: 2 or more gives debug output.
    int _verbose;

    /// Whether to dump all SWF actions
    bool _actiondump;

    /// Whether to dump all networking actions
    bool _network;

    /// Whether to dump parser output
    bool _parserdump;

    /// The state of the log file.
    FileState _state;

    bool _stamp;

    /// Whether to write the log file to disk.
    bool _write;

    std::string _filespec;

    std::string _logFilename;
    
    logListener _listener;

};

DSOEXPORT void processLog_network(const boost::format& fmt);
DSOEXPORT void processLog_error(const boost::format& fmt);
DSOEXPORT void processLog_unimpl(const boost::format& fmt);
DSOEXPORT void processLog_trace(const boost::format& fmt);
DSOEXPORT void processLog_debug(const boost::format& fmt);
DSOEXPORT void processLog_action(const boost::format& fmt);
DSOEXPORT void processLog_parse(const boost::format& fmt);
DSOEXPORT void processLog_security(const boost::format& fmt);
DSOEXPORT void processLog_swferror(const boost::format& fmt);
DSOEXPORT void processLog_aserror(const boost::format& fmt);
DSOEXPORT void processLog_abc(const boost::format& fmt);

template <typename FuncType>
inline void
log_impl(boost::format& fmt, FuncType func)
{
    func(fmt);
}

template<typename FuncType, typename Arg, typename... Args>
inline void
log_impl(boost::format& fmt, FuncType processFunc, Arg arg, Args... args)
{
    fmt % arg;
    log_impl(fmt, processFunc, args...);
}

template<typename StringType, typename FuncType, typename... Args>
inline void
log_impl(StringType msg, FuncType func, Args... args)
{
    boost::format fmt(msg);
    using namespace boost::io;
    fmt.exceptions(all_error_bits ^ (too_many_args_bit |
                                   too_few_args_bit |
                                   bad_format_string_bit));
    log_impl(fmt, func, args...);
}

template<typename StringType, typename... Args>
inline void log_network(StringType msg, Args... args)
{
    log_impl(msg, processLog_network, args...);
}

template<typename StringType, typename... Args>
inline void log_error(StringType msg, Args... args)
{
    log_impl(msg, processLog_error, args...);
}

template<typename StringType, typename... Args>
inline void log_unimpl(StringType msg, Args... args)
{
    log_impl(msg, processLog_unimpl, args...);
}

template<typename StringType, typename... Args>
inline void log_trace(StringType msg, Args... args)
{
    log_impl(msg, processLog_trace, args...);
}

template<typename StringType, typename... Args>
inline void log_debug(StringType msg, Args... args)
{
    log_impl(msg, processLog_debug, args...);
}

template<typename StringType, typename... Args>
inline void log_action(StringType msg, Args... args)
{
    log_impl(msg, processLog_action, args...);
}

template<typename StringType, typename... Args>
inline void log_parse(StringType msg, Args... args)
{
    log_impl(msg, processLog_parse, args...);
}

template<typename StringType, typename... Args>
inline void log_security(StringType msg, Args... args)
{
    log_impl(msg, processLog_security, args...);
}

template<typename StringType, typename... Args>
inline void log_swferror(StringType msg, Args... args)
{
    log_impl(msg, processLog_swferror, args...);
}

template<typename StringType, typename... Args>
inline void log_aserror(StringType msg, Args... args)
{
    log_impl(msg, processLog_aserror, args...);
}

template<typename StringType, typename... Args>
inline void log_abc(StringType msg, Args... args)
{
    log_impl(msg, processLog_abc, args...);
}

/// Convert a sequence of bytes to hex or ascii format.
//
/// @param bytes    the array of bytes to process
/// @param length   the number of bytes to read. Callers are responsible
///                 for checking that length does not exceed the array size.
/// @param ascii    whether to return in ascii or space-separated hex format.
/// @return         a string representation of the byte sequence.
DSOEXPORT std::string hexify(const unsigned char *bytes, size_t length,
        bool ascii);

// Define to 0 to completely remove parse debugging at compile-time
#ifndef VERBOSE_PARSE
#define VERBOSE_PARSE 1
#endif

// Define to 0 to completely remove action debugging at compile-time
#ifndef VERBOSE_ACTION
#define VERBOSE_ACTION 1
#endif

// Define to 0 to remove ActionScript errors verbosity at compile-time
#ifndef VERBOSE_ASCODING_ERRORS
#define VERBOSE_ASCODING_ERRORS  1
#endif

// Define to 0 this to remove invalid SWF verbosity at compile-time
#ifndef VERBOSE_MALFORMED_SWF
#define VERBOSE_MALFORMED_SWF 1
#endif

// Define to 0 this to remove Networking verbosity at compile-time
#ifndef VERBOSE_NETWORKING
#define VERBOSE_NETWORKING 1
#endif

#if VERBOSE_PARSE
#define IF_VERBOSE_PARSE(x) do { if ( LogFile::getDefaultInstance().getParserDump() ) { x; } } while (0);
#else
#define IF_VERBOSE_PARSE(x)
#endif

#if VERBOSE_ACTION
#define IF_VERBOSE_ACTION(x) do { if ( LogFile::getDefaultInstance().getActionDump() ) { x; } } while (0);
#else
#define IF_VERBOSE_ACTION(x)
#endif

#if VERBOSE_ACTION
#define IF_VERBOSE_NETWORK(x) do { if ( LogFile::getDefaultInstance().getNetwork() ) { x; } } while (0);
#else
#define IF_VERBOSE_NETWORK(x)
#endif

#if VERBOSE_ASCODING_ERRORS
// TODO: check if it's worth to check verbosity level too...
#define IF_VERBOSE_ASCODING_ERRORS(x) { if ( gnash::RcInitFile::getDefaultInstance().showASCodingErrors() ) { x; } }
#else
#define IF_VERBOSE_ASCODING_ERRORS(x)
#endif

#if VERBOSE_MALFORMED_SWF
// TODO: check if it's worth to check verbosity level too... 
#define IF_VERBOSE_MALFORMED_SWF(x) { if ( gnash::RcInitFile::getDefaultInstance().showMalformedSWFErrors() ) { x; } }
#else
#define IF_VERBOSE_MALFORMED_SWF(x)
#endif

class DSOEXPORT HostFunctionReport
{
public:
    // Only print function tracing messages when multiple -v
    // options have been supplied. 
    HostFunctionReport() : _func(nullptr) {
        log_debug("entering");
    }

    HostFunctionReport(const char* func) : _func(func) {
        if (func) {
            log_debug("%s enter", func);
        }
        else {
            log_debug("No Function Name! enter");
        }
    }
    ~HostFunctionReport() {
        log_debug("%s returning", _func);
    }
private:
    const char* _func;
};

#ifndef HAVE_FUNCTION
    #ifndef HAVE_func
        #define dummystr(x) # x
        #define dummyestr(x) dummystr(x)
        #define __FUNCTION__ __FILE__ ":" dummyestr(__LINE__)
    #else
        #define __FUNCTION__ __func__    
    #endif
#endif

#ifndef HAVE_PRETTY_FUNCTION
    #define __PRETTY_FUNCTION__ __FUNCTION__
#endif

#if defined(__cplusplus) && defined(__GNUC__)
#define GNASH_REPORT_FUNCTION   \
    const gnash::HostFunctionReport hfr(__PRETTY_FUNCTION__)
#define GNASH_REPORT_RETURN
#else
#define GNASH_REPORT_FUNCTION \
    gnash::log_debug("entering")

#define GNASH_REPORT_RETURN \
    gnash::log_debug("returning")
#endif

}

#endif // GNASH_LOG_H


// Local Variables:
// mode: C++
// indent-tabs-mode: nil
// End:
