/*
 * Copyright (C) Texas Instruments - http://www.ti.com/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef DEBUG_UTILS_H
#define DEBUG_UTILS_H

#include <android/log.h>
#include <utils/threads.h>
#include <utils/Vector.h>




namespace Ti {




// use 2 space characters for call stack indent
static const int kFunctionLoggerIndentSize = 2;




template <int Size = kFunctionLoggerIndentSize>
class IndentString
{
public:
    IndentString(int length);

    const char * string() const;

private:
    int calculateOffset(int length) const;

private:
    const int mOffset;
};




class Debug
{
public:
    static Debug * instance();

    int offsetForCurrentThread();
    void log(int priority, const char * format, ...);

private:
    class ThreadInfo
    {
    public:
        ThreadInfo() :
            threadId(0), callOffset(0)
        {}

        volatile int32_t threadId;
        int callOffset;
    };

    class Data : public android::RefBase
    {
    public:
        android::Vector<ThreadInfo*> threads;
    };

private:
    // called from FunctionLogger
    void increaseOffsetForCurrentThread();
    void decreaseOffsetForCurrentThread();

private:
    Debug();

    void grow();
    ThreadInfo * registerThread(Data * data, int32_t threadId);
    ThreadInfo * findCurrentThreadInfo();
    void addOffsetForCurrentThread(int offset);

private:
    static Debug sInstance;

    mutable android::Mutex mMutex;
    android::sp<Data> mData;

    friend class FunctionLogger;
};




class FunctionLogger
{
public:
    FunctionLogger(const char * file, int line, const char * function);
    ~FunctionLogger();

    void setExitLine(int line);

private:
    const char * const mFile;
    const int mLine;
    const char * const mFunction;
    const void * const mThreadId;
    int mExitLine;
};




#ifdef TI_UTILS_FUNCTION_LOGGER_ENABLE
#   define LOG_FUNCTION_NAME Ti::FunctionLogger __function_logger_instance(__FILE__, __LINE__, __FUNCTION__);
#   define LOG_FUNCTION_NAME_EXIT __function_logger_instance.setExitLine(__LINE__);
#else
#   define LOG_FUNCTION_NAME int __function_logger_instance;
#   define LOG_FUNCTION_NAME_EXIT (void*)__function_logger_instance;
#endif

#ifdef TI_UTILS_DEBUG_USE_TIMESTAMPS
    // truncate timestamp to 1000 seconds to fit into 6 characters
#   define TI_UTILS_DEBUG_TIMESTAMP_TOKEN "[%06d] "
#   define TI_UTILS_DEBUG_TIMESTAMP_VARIABLE static_cast<int>(nanoseconds_to_milliseconds(systemTime()) % 1000000),
#else
#   define TI_UTILS_DEBUG_TIMESTAMP_TOKEN
#   define TI_UTILS_DEBUG_TIMESTAMP_VARIABLE
#endif




#define DBGUTILS_LOGV_FULL(priority, file, line, function, format, ...)       \
    do                                                                        \
    {                                                                         \
        Ti::Debug * const debug = Ti::Debug::instance();                      \
        debug->log(priority, format,                                          \
                TI_UTILS_DEBUG_TIMESTAMP_VARIABLE                             \
                reinterpret_cast<int>(androidGetThreadId()),                  \
                Ti::IndentString<>(debug->offsetForCurrentThread()).string(), \
                file, line, function, __VA_ARGS__);                           \
    } while (0)

#define DBGUTILS_LOGV(...) DBGUTILS_LOGV_FULL(ANDROID_LOG_VERBOSE, __FILE__, __LINE__, __FUNCTION__, TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s  %s:%d %s - " __VA_ARGS__, "")
#define DBGUTILS_LOGD(...) DBGUTILS_LOGV_FULL(ANDROID_LOG_DEBUG,   __FILE__, __LINE__, __FUNCTION__, TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s  %s:%d %s - " __VA_ARGS__, "")
#define DBGUTILS_LOGI(...) DBGUTILS_LOGV_FULL(ANDROID_LOG_INFO,    __FILE__, __LINE__, __FUNCTION__, TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s  %s:%d %s - " __VA_ARGS__, "")
#define DBGUTILS_LOGW(...) DBGUTILS_LOGV_FULL(ANDROID_LOG_WARN,    __FILE__, __LINE__, __FUNCTION__, TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s  %s:%d %s - " __VA_ARGS__, "")
#define DBGUTILS_LOGE(...) DBGUTILS_LOGV_FULL(ANDROID_LOG_ERROR,   __FILE__, __LINE__, __FUNCTION__, TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s  %s:%d %s - " __VA_ARGS__, "")
#define DBGUTILS_LOGF(...) DBGUTILS_LOGV_FULL(ANDROID_LOG_FATAL,   __FILE__, __LINE__, __FUNCTION__, TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s  %s:%d %s - " __VA_ARGS__, "")

#define DBGUTILS_LOGVA DBGUTILS_LOGV
#define DBGUTILS_LOGVB DBGUTILS_LOGV

#define DBGUTILS_LOGDA DBGUTILS_LOGD
#define DBGUTILS_LOGDB DBGUTILS_LOGD

#define DBGUTILS_LOGEA DBGUTILS_LOGE
#define DBGUTILS_LOGEB DBGUTILS_LOGE

// asserts
#define _DBGUTILS_PLAIN_ASSERT(condition)                              \
    do                                                                 \
    {                                                                  \
        if ( !(condition) )                                            \
        {                                                              \
            __android_log_print(ANDROID_LOG_FATAL, "Ti::Debug",        \
                    "Condition failed: " #condition);                  \
            __android_log_print(ANDROID_LOG_FATAL, "Ti::Debug",        \
                    "Aborting process...");                            \
            abort();                                                   \
        }                                                              \
    } while (0)

#define _DBGUTILS_PLAIN_ASSERT_X(condition, ...)                       \
    do                                                                 \
    {                                                                  \
        if ( !(condition) )                                            \
        {                                                              \
            __android_log_print(ANDROID_LOG_FATAL, "Ti::Debug",        \
                    "Condition failed: " #condition ": " __VA_ARGS__); \
            __android_log_print(ANDROID_LOG_FATAL, "Ti::Debug",        \
                    "Aborting process...");                            \
            abort();                                                   \
        }                                                              \
    } while (0)

#define DBGUTILS_ASSERT(condition)                                           \
    do                                                                       \
    {                                                                        \
        if ( !(condition) )                                                  \
        {                                                                    \
            DBGUTILS_LOGF("Condition failed: " #condition);                  \
            DBGUTILS_LOGF("Aborting process...");                            \
            abort();                                                         \
        }                                                                    \
    } while (0)

#define DBGUTILS_ASSERT_X(condition, ...)                                    \
    do                                                                       \
    {                                                                        \
        if ( !(condition) )                                                  \
        {                                                                    \
            DBGUTILS_LOGF("Condition failed: " #condition ": " __VA_ARGS__); \
            DBGUTILS_LOGF("Aborting process...");                            \
            abort();                                                         \
        }                                                                    \
    } while (0)




static const int kIndentStringMaxLength = 128;

template <int Size>
inline int IndentString<Size>::calculateOffset(const int length) const
{
    const int offset = kIndentStringMaxLength - length*Size;
    return offset < 0 ? 0 : offset;
}

template <int Size>
inline IndentString<Size>::IndentString(const int length) :
    mOffset(calculateOffset(length))
{}

template <int Size>
inline const char * IndentString<Size>::string() const
{
    extern const char sIndentStringBuffer[];
    return sIndentStringBuffer + mOffset;
}




inline Debug * Debug::instance()
{ return &sInstance; }


inline Debug::ThreadInfo * Debug::findCurrentThreadInfo()
{
    // retain reference to threads data
    android::sp<Data> data = mData;

    // iterate over threads to locate thread id,
    // this is safe from race conditions because each thread
    // is able to modify only his own ThreadInfo structure
    const int32_t threadId = reinterpret_cast<int32_t>(androidGetThreadId());
    const int size = int(data->threads.size());
    for ( int i = 0; i < size; ++i )
    {
        ThreadInfo * const threadInfo = data->threads.itemAt(i);
        if ( threadInfo->threadId == threadId )
            return threadInfo;
    }

    // this thread has not been registered yet,
    // try to fing empty thread info slot
    while ( true )
    {
        ThreadInfo * const threadInfo = registerThread(data.get(), threadId);
        if ( threadInfo )
            return threadInfo;

        // failed registering thread, because all slots are occupied
        // grow the data and try again
        grow();

        data = mData;
    }

    // should never reach here
    _DBGUTILS_PLAIN_ASSERT(false);
    return 0;
}


inline void Debug::addOffsetForCurrentThread(const int offset)
{
    if ( offset == 0 )
        return;

    ThreadInfo * const threadInfo = findCurrentThreadInfo();
    _DBGUTILS_PLAIN_ASSERT(threadInfo);

    threadInfo->callOffset += offset;

    if ( threadInfo->callOffset == 0 )
    {
        // thread call stack has dropped to zero, unregister it
        android_atomic_acquire_store(0, &threadInfo->threadId);
    }
}


inline int Debug::offsetForCurrentThread()
{
#ifdef TI_UTILS_FUNCTION_LOGGER_ENABLE
    ThreadInfo * const threadInfo = findCurrentThreadInfo();
    _DBGUTILS_PLAIN_ASSERT(threadInfo);

    return threadInfo->callOffset;
#else
    return 0;
#endif
}


inline void Debug::increaseOffsetForCurrentThread()
{
#ifdef TI_UTILS_FUNCTION_LOGGER_ENABLE
    addOffsetForCurrentThread(1);
#endif
}


inline void Debug::decreaseOffsetForCurrentThread()
{
#ifdef TI_UTILS_FUNCTION_LOGGER_ENABLE
    addOffsetForCurrentThread(-1);
#endif
}


inline void Debug::log(const int priority, const char * const format, ...)
{
    va_list args;
    va_start(args, format);
    __android_log_vprint(priority, LOG_TAG, format, args);
    va_end(args);
}




inline FunctionLogger::FunctionLogger(const char * const file, const int line, const char * const function) :
    mFile(file), mLine(line), mFunction(function), mThreadId(androidGetThreadId()), mExitLine(-1)
{
    Debug * const debug = Debug::instance();
    debug->increaseOffsetForCurrentThread();
    android_printLog(ANDROID_LOG_DEBUG, LOG_TAG,
            TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s+ %s:%d %s - ENTER",
            TI_UTILS_DEBUG_TIMESTAMP_VARIABLE
            (int)mThreadId, IndentString<>(debug->offsetForCurrentThread()).string(),
            mFile, mLine, mFunction);
}


inline FunctionLogger::~FunctionLogger()
{
    Debug * const debug = Debug::instance();
    android_printLog(ANDROID_LOG_DEBUG, LOG_TAG,
            TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s- %s:%d %s - EXIT",
            TI_UTILS_DEBUG_TIMESTAMP_VARIABLE
            (int)mThreadId, IndentString<>(debug->offsetForCurrentThread()).string(),
            mFile, mExitLine == -1 ? mLine : mExitLine, mFunction);
    debug->decreaseOffsetForCurrentThread();
}


inline void FunctionLogger::setExitLine(const int line)
{
    if ( mExitLine != -1 )
    {
        Debug * const debug = Debug::instance();
        android_printLog(ANDROID_LOG_DEBUG, LOG_TAG,
                TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s  %s:%d %s - Double function exit trace detected. Previous: %d",
                TI_UTILS_DEBUG_TIMESTAMP_VARIABLE
                (int)mThreadId, IndentString<>(debug->offsetForCurrentThread()).string(),
                mFile, line, mFunction, mExitLine);
    }

    mExitLine = line;
}




} // namespace Ti




#endif //DEBUG_UTILS_H