//SPDX-License-Identifier:MediaTekProprietary
/* Copyright Statement:
 *
 * This software/firmware/tool and related documentation ("MediaTek Software") are
 * protected under relevant copyright laws. The information contained herein is
 * confidential and proprietary to MediaTek Inc. and/or its licensors. Without
 * the prior written permission of MediaTek inc. and/or its licensors, any
 * reproduction, modification, use or disclosure of MediaTek Software, and
 * information contained herein, in whole or in part, shall be strictly
 * prohibited.
 *
 * MediaTek Inc. (C) 2019-2020. All rights reserved.
 *
 * BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
 * THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
 * RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER
 * ON AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL
 * WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NONINFRINGEMENT. NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH
 * RESPECT TO THE SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY,
 * INCORPORATED IN, OR SUPPLIED WITH THE MEDIATEK SOFTWARE, AND RECEIVER AGREES
 * TO LOOK ONLY TO SUCH THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO.
 * RECEIVER EXPRESSLY ACKNOWLEDGES THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO
 * OBTAIN FROM ANY THIRD PARTY ALL PROPER LICENSES CONTAINED IN MEDIATEK
 * SOFTWARE. MEDIATEK SHALL ALSO NOT BE RESPONSIBLE FOR ANY MEDIATEK SOFTWARE
 * RELEASES MADE TO RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR
 * STANDARD OR OPEN FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S
 * ENTIRE AND CUMULATIVE LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE
 * RELEASED HEREUNDER WILL BE, AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE
 * MEDIATEK SOFTWARE AT ISSUE, OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE
 * CHARGE PAID BY RECEIVER TO MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
 *
 * The following software/firmware and/or related documentation ("MediaTek
 * Software") have been modified by MediaTek Inc. All revisions are subject to
 * any receiver's applicable license agreements with MediaTek Inc.
 */

#ifndef __SOUNDBAR_DEBUG_H__
#define __SOUNDBAR_DEBUG_H__

#include <stdint.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/file.h>

inline static int64_t log_systemTime() /* nanoseconds */
{
    struct timespec t;
    t.tv_sec = t.tv_nsec = 0;
    clock_gettime(CLOCK_MONOTONIC, &t);
    return (int64_t)(t.tv_sec)*1000000000LL + t.tv_nsec;
}
#define log_ns2s(v)   (v/1000000000.0)


struct sndbar_dbg_log_t
{
    char check_point[128];
    char file_path[128];

    int max_num;
    int cur_num;
} _sndbar_dbg_log_hdl_;

static void sndbar_dbg_log_file_init (char* check_point, int max_num)
{
    _sndbar_dbg_log_hdl_.max_num = max_num;
    _sndbar_dbg_log_hdl_.cur_num = 0;
    int ret = 0;
    if (max_num < 1) return;
    strcpy (_sndbar_dbg_log_hdl_.check_point, check_point);

    static struct timespec st;
    clock_gettime(CLOCK_MONOTONIC, &st);

    ret = snprintf(_sndbar_dbg_log_hdl_.file_path, 128, "/tmp/sndbar_log_%s.%ld.%ld",
            check_point,
            st.tv_sec, st.tv_nsec);
    if(ret < 0)
    {
        printf ("[sndbar_dbg] snprintf fail\n");
    }

    printf ("[sndbar_dbg] log to %s\n", _sndbar_dbg_log_hdl_.file_path);
}

static void sndbar_dbg_log_file_add (char* msg)
{
    FILE *mfp = NULL;
    int ret = 0;
    if ((_sndbar_dbg_log_hdl_.max_num == -1) || (_sndbar_dbg_log_hdl_.max_num > _sndbar_dbg_log_hdl_.cur_num))
    {
        mfp = fopen(_sndbar_dbg_log_hdl_.file_path, "a+");
        if(NULL == mfp)
        {
           printf("[sndbar_dbg] fopen fail\n");
           return -1;
        }
        ret = fputs (msg, mfp);
        if(ret < 0)
        {
            printf("[sndbar_dbg] fputs fail,ret:%d\n",ret);
        }
        ret = fclose(mfp);
        if(ret != 0)
        {
           printf("[sndbar_dbg] fclose fail, ret:%d\n",ret);
        }

        _sndbar_dbg_log_hdl_.cur_num++;
    }
}

#define SNDBAR_LOG(level, log_level, io_type, fmt, arg...) \
    do { \
        if ((level) <= (log_level)) { \
            if (io_type == 0) { \
                printf("[%.6f] " fmt "\n", log_ns2s(log_systemTime()), ##arg);  \
            } \
            else { \
                char msg[128]; \
                int ret = 0; \
                ret = snprintf (msg, 127, "[%.6f] " fmt "\n", log_ns2s(log_systemTime()), ##arg); \
                if(ret > 0){ \
                    sndbar_dbg_log_file_add (msg); \
                } \
            } \
        } \
    } while(0)


#define SNDBAR_LOG_OFF     0    /* no log */
#define SNDBAR_LOG_FATAL   1
#define SNDBAR_LOG_ERR     2
#define SNDBAR_LOG_WARN    3
#define SNDBAR_LOG_INFO    4
#define SNDBAR_LOG_DEBUG   5
#define SNDBAR_LOG_TRACE   6    /* Trace code flow */
#define SNDBAR_LOG_ALL     7    /* Capture any log */
#define SNDBAR_LOG_DEFAULT LOG_INFO

#define SNDBAR_LOGA(log_level, fmt, arg...) \
    SNDBAR_LOG(SNDBAR_LOG_ALL, log_level, 0, "ALL: " fmt, ##arg)

#define SNDBAR_LOGT(log_level, fmt, arg...) \
    SNDBAR_LOG(SNDBAR_LOG_TRACE, log_level, 0, "TRACE : " fmt, ##arg)

#define SNDBAR_LOGD(log_level, fmt, arg...) \
    SNDBAR_LOG(SNDBAR_LOG_DEBUG, log_level, 0, "DEBUG : " fmt, ##arg)

#define SNDBAR_LOGI(log_level, fmt, arg...) \
    SNDBAR_LOG(SNDBAR_LOG_INFO, log_level, 0, "INFO : " fmt, ##arg)

#define SNDBAR_LOGW(log_level, fmt, arg...) \
    SNDBAR_LOG(SNDBAR_LOG_WARN, log_level, 0, "WARN : " fmt, ##arg)

#define SNDBAR_LOGE(log_level, fmt, arg...) \
    SNDBAR_LOG(SNDBAR_LOG_ERR, log_level, 0, "ERR : " fmt, ##arg)

#define SNDBAR_LOGF(log_level, fmt, arg...) \
    SNDBAR_LOG(SNDBAR_LOG_FATAL, log_level, 0, "FATAL : " fmt, ##arg)


#define SNDBAR_LOGI_FILE(log_level, fmt, arg...) \
    SNDBAR_LOG(SNDBAR_LOG_INFO, log_level, 1, "INFO : " fmt, ##arg)

#define _SNDBAR_MSG_LEN_ 128
static char* sndbar_dbg_conf_get_value_by_key (char* key, char* line)
{
    char* strpos = NULL;

    if (key == NULL || line == NULL)
        return NULL;

    int i = strlen (line);
    for (; i >= 0; i--)
    {
        if (line[i] == '\n' || line[i] == '\r')
        {
            line[i] = 0;
        }
    }

    strpos = strstr (line, key);
    if (NULL == strpos)
        return NULL;

    if (strpos != line)
        return NULL;

    if (strpos[strlen (key)] != ':')
    {
        return NULL;
    }

    strpos = strpos + strlen (key) + 1;
    return strpos;
}

static int sndbar_dbg_conf_get_value (char* conf_path, char* key, char* default_value,
                                        char* value)
{
    FILE *stream;
    char line[_SNDBAR_MSG_LEN_ * 2];
    int log_level;
    int ret = 0;

    if (NULL == conf_path || NULL == key || NULL == default_value || NULL == value)
    {
        return -1;
    }

    if (access (conf_path, F_OK) != 0)
    {
        strncpy (value, default_value, strlen(default_value) + 1);
        return 0;
    }

    stream = fopen (conf_path, "r");
    if (stream == NULL)
    {
        strncpy (value, default_value, strlen(default_value) + 1);
        return 0;
    }
    flock(fileno(stream), LOCK_SH);

    while (fgets (line, _SNDBAR_MSG_LEN_ * 2, stream) != NULL)
    {
        char* tmp = sndbar_dbg_conf_get_value_by_key (key, line);
        if (tmp != NULL)
        {
            strncpy (value, tmp, strlen(tmp) + 1);
            printf ("[sndbar_dbg](%s) get key[%s] => value[%s]\n", conf_path, key, value);
            ret = fclose (stream);
            if(ret != 0)
            {
               printf ("[sndbar_dbg] fclose fail, ret:%d\n",ret);
            }
            return 1;
        }
    }

    strncpy (value, default_value, strlen(default_value) + 1);
    ret = fclose (stream);
    if(ret != 0)
    {
       printf ("[sndbar_dbg] fclose fail, ret:%d\n",ret);
    }
    return 0;
}

static int sndbar_dbg_conf_get_value_int (char* conf_path, char* key, int default_value)
{
    char value[_SNDBAR_MSG_LEN_] = {'\0'};
    char value_default_str [_SNDBAR_MSG_LEN_] = {'\0'};
    int ret = 0;
    ret = snprintf (value_default_str, _SNDBAR_MSG_LEN_, "%d", default_value);
    if(ret < 0)
    {
        printf ("[sndbar_dbg] snprintf fail\n");
        return -1;
    }

    sndbar_dbg_conf_get_value (conf_path, key, value_default_str, value);
    return atoi (value);
}

static int sndbar_dbg_conf_get_log_level (char* conf_path, int default_value)
{
    return sndbar_dbg_conf_get_value_int (conf_path, "loglevel", default_value);
}

#if 1
struct sndbar_dbg_dump_t
{
    int cfg;
    int timeout_level;
    char dump_point[128];
    char dump_path[128];

    long count;
    long last_dump_time;
};

static void sndbar_dbg_dump_set_unique_file_path (const char* p0, const char* p1, char* buffer, int len)
{
    static struct timespec st;
    clock_gettime(CLOCK_MONOTONIC, &st);
    int ret = 0;
    ret = snprintf(buffer, len, "%s%s_%ld_%ld",
            p0,
            p1,
            st.tv_sec, st.tv_nsec);
    if(ret < 0)
    {
        printf ("[sndbar_dbg] snprintf fail\n");
        return -1;
    }
}

static void sndbar_dbg_dump_init (struct sndbar_dbg_dump_t* self, int cfg, int timeout_level, char* dump_point)
{
    self->cfg = cfg;
    if (self->cfg == 0)
    {
        return;
    }

    self->timeout_level = timeout_level;
    strncpy (self->dump_point, dump_point, 128);

    self->count = 0;
    self->last_dump_time = 0;

    sndbar_dbg_dump_set_unique_file_path ("/tmp/sndbar_dump_", self->dump_point, self->dump_path, 128);
    printf ("[sndbar_dbg] dump to %s\n", self->dump_path);
}

static void sndbar_dbg_dump_record (struct sndbar_dbg_dump_t* self, void* pbuf, int pbuf_size)
{
    long cur_time;
    struct timespec cur_time_ts;
    int ret = 0;
    if (self->cfg == 0)
    {
        return;
    }

    /* check if need to change dump path */
    clock_gettime (CLOCK_MONOTONIC, &cur_time_ts);
    cur_time = (cur_time_ts.tv_sec * 1000) + (cur_time_ts.tv_nsec / (1000 * 1000));
    if (((cur_time - self->last_dump_time) > self->timeout_level) && (self->last_dump_time != 0))
    {
        sndbar_dbg_dump_set_unique_file_path ("/tmp/sndbar_dump_", self->dump_point, self->dump_path, 128);
        printf ("[sndbar_dbg] (%ld -> %ld ms) dump to %s\n", self->last_dump_time, cur_time, self->dump_path);
    }
    self->last_dump_time = cur_time;

    {
        FILE *mfp = NULL;
        mfp = fopen(self->dump_path, "a+");
        if(NULL == mfp)
        {
           printf ("[sndbar_dbg] fopen fail\n");
           return -1;
        }
        ret = fwrite(pbuf, pbuf_size, 1, mfp);
        if(ret != pbuf_size)
        {
           printf ("[sndbar_dbg] fwrite fail, ret:%d\n",ret);
        }
        ret = fclose(mfp);
        if(ret != 0)
        {
           printf ("[sndbar_dbg] fclose fail, ret:%d\n",ret);
           return -1;
        }
    }

    self->count++;
}
#endif
#if  1
/* #define MET_SUPPORT */
#ifdef MET_SUPPORT
#define MET_OFFSET 65535
#else
#define MET_OFFSET 0
#endif

static int atrace_marker_fd = -1;
static int open_atrace_file()
{
    if (atrace_marker_fd == -1) {
        atrace_marker_fd =
            open("/sys/kernel/debug/tracing/trace_marker",
            O_WRONLY | O_CLOEXEC);
        if (atrace_marker_fd == -1) {
            return -1;
        }
    }
    return 0;
}

static int close_atrace_file()
{
    if (atrace_marker_fd != -1) {
        if (close(atrace_marker_fd)) {
            return -1;
        } else
            atrace_marker_fd = -1;
    }
    return 0;
}

#define TRACE_BUF_SIZE 500
static void tracing_mark_write(const char *fmt, ...)
{
    char buffer[TRACE_BUF_SIZE];
    int len;
    va_list ap;

    if (-1 == atrace_marker_fd)
        return;

    va_start(ap, fmt);
    len = vsnprintf(buffer, TRACE_BUF_SIZE, fmt, ap);
    va_end(ap);
    if(len < 0)
        return;

    write(atrace_marker_fd, buffer, len);
}


static void atrace_begin(const char *name)
{
    tracing_mark_write("[B]%s\n", name);
}

static void atrace_end(const char *name)
{
    tracing_mark_write("[E]%s\n", name);
}

#endif

#endif // 
