//
// tardy - a tar post-processor
// Copyright (C) 1991-2009, 2011, 2012 Peter Miller
//
// 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, see <http://www.gnu.org/licenses/>.
//

#include <libtardy/ac/stdarg.h>
#include <libtardy/ac/stddef.h>
#include <libtardy/ac/stdio.h>
#include <libtardy/ac/string.h>
#include <libexplain/fflush.h>
#include <libexplain/program_name.h>

#include <libtardy/rcstring.h>
#include <libtardy/trace.h>


#define INDENT 2

struct known_t
{
    known_t() :
        flag(0),
        flag_p(0),
        next(0)
    {
    }

    rcstring filename;
    int flag;
    int *flag_p;
    known_t *next;
};

static rcstring file_name;
static int line_number;
static int page_width;
static known_t *known;
static int depth;


int
trace_pretest(const char *file, int *result)
{
    rcstring s = rcstring(file).basename();
    known_t *kp = known;
    for (; kp; kp = kp->next)
    {
        if (s == kp->filename)
        {
            break;
        }
    }
    if (!kp)
    {
        kp = new known_t;
        kp->filename = s;
        kp->next = known;
        kp->flag = 2; // disabled
        known = kp;
    }
    kp->flag_p = result;
    *result = kp->flag;
    return *result;
}


void
trace_where(const char *file, int line)
{
    file_name = rcstring(file).basename();
    line_number = line;
}


static void
trace_putchar(int c)
{
    static char buffer[200];
    static char *cp;
    static int in_col;
    static int out_col;

    if (!page_width)
    {
        // don't use last column, many terminals are dumb
        page_width = 79;
        // allow for progname, filename and line number (8 each)
        page_width -= 24;
        if (page_width < 16)
            page_width = 16;
    }
    if (!cp)
    {
        strcpy(buffer, explain_program_name_get());
        cp = buffer + strlen(buffer);
        if (cp > buffer + 6)
        cp = buffer + 6;
        *cp++ = ':';
        *cp++ = '\t';
        strcpy(cp, file_name.c_str());
        cp += file_name.size();
        *cp++ = ':';
        *cp++ = '\t';
        snprintf(cp, buffer + sizeof(buffer) - cp, "%d:\t", line_number);
        cp += strlen(cp);
        in_col = 0;
        out_col = 0;
    }
    switch (c)
    {
    case '\n':
        *cp++ = '\n';
        *cp = 0;
        explain_fflush_or_die(stdout);
        fputs(buffer, stderr);
        explain_fflush_or_die(stderr);
        cp = 0;
        break;

    case ' ':
        if (out_col)
            ++in_col;
        break;

    case '\t':
        if (out_col)
            in_col = (in_col/INDENT + 1) * INDENT;
        break;

    case '}':
    case ')':
    case ']':
        if (depth > 0)
            --depth;
        // fall through

    default:
        if (!out_col)
        {
            if (c != '#')
                // modulo so never too long
                in_col = (INDENT * depth) % page_width;
            else
                in_col = 0;
        }
        if (in_col >= page_width)
        {
            trace_putchar('\n');
            trace_putchar(c);
            return;
        }
        while (((out_col + 8) & -8) <= in_col && out_col + 1 < in_col)
        {
            *cp++ = '\t';
            out_col = (out_col + 8) & -8;
        }
        while (out_col < in_col)
        {
            *cp++ = ' ';
            ++out_col;
        }
        if (c == '{' || c == '(' || c == '[')
            ++depth;
        *cp++ = c;
        in_col++;
        out_col++;
        break;
    }
}


void
trace_printf(const char *s, ...)
{
    va_list ap;
    va_start(ap, s);
    char buffer[2000];
    vsnprintf(buffer, sizeof(buffer), s, ap);
    va_end(ap);
    for (s = buffer; *s; ++s)
        trace_putchar(*s);
}


void
trace_enable(const char *file)
{
    rcstring s = rcstring(file).basename();
    known_t *kp;
    for (kp = known; kp; kp = kp->next)
    {
        if (s == kp->filename)
        {
            break;
        }
    }
    if (!kp)
    {
        kp = new known_t;
        kp->filename = s;
        kp->next = known;
        known = kp;
    }
    kp->flag = 3; // enabled
    if (kp->flag_p)
        *kp->flag_p = kp->flag;
}


void
trace_char_real(char *name, char *vp)
{
    trace_printf("%s = '", name);
    if (*vp < ' ' || *vp > '~' || strchr("(){}[]", *vp))
    {
        const char *s = strchr("\bb\nn\tt\rr\ff", *vp);
        if (s)
        {
            trace_putchar('\\');
            trace_putchar(s[1]);
        }
        else
            trace_printf("\\%03o", (unsigned char)*vp);
    }
    else
    {
        if (strchr("'\\", *vp))
            trace_putchar('\\');
        trace_putchar(*vp);
    }
    trace_printf("'; /* 0x%02X, %d */\n", (unsigned char)*vp, *vp);
}


void
trace_char_unsigned_real(char *name, unsigned char *vp)
{
    trace_printf("%s = '", name);
    if (*vp < ' ' || *vp > '~' || strchr("(){}[]", *vp))
    {
        const char *s = strchr("\bb\nn\tt\rr\ff", *vp);
        if (s)
        {
            trace_putchar('\\');
            trace_putchar(s[1]);
        }
        else
            trace_printf("\\%03o", *vp);
    }
    else
    {
        if (strchr("'\\", *vp))
            trace_putchar('\\');
        trace_putchar(*vp);
    }
    trace_printf("'; /* 0x%02X, %d */\n", *vp, *vp);
}


void
trace_int_real(char *name, int *vp)
{
    trace_printf("%s = %d;\n", name, *vp);
}


void
trace_int_unsigned_real(char *name, unsigned int *vp)
{
    trace_printf("%s = %u;\n", name, *vp);
}


void
trace_long_real(char *name, long *vp)
{
    trace_printf("%s = %ld;\n", name, *vp);
}


void
trace_long_unsigned_real(char *name, unsigned long *vp)
{
    trace_printf("%s = %lu;\n", name, *vp);
}


void
trace_pointer_real(char *name, void *vptrptr)
{
    void **ptr_ptr = (void **)vptrptr;
    void *ptr = *ptr_ptr;
    if (!ptr)
        trace_printf("%s = NULL;\n", name);
    else
        trace_printf("%s = 0x%08lX;\n", name, (long)ptr);
}


void
trace_short_real(char *name, const short *vp)
{
    trace_printf("%s = %hd;\n", name, *vp);
}


void
trace_short_unsigned_real(const char *name, const unsigned short *vp)
{
    trace_printf("%s = %hu;\n", name, *vp);
}


void
trace_string_real(const char *name, const char *vp)
{
    trace_printf("%s = ", name);
    if (!vp)
    {
        trace_printf("NULL;\n");
        return;
    }
    trace_printf("\"");
    long count = 0;
    for (const char *s = vp; *s; ++s)
    {
        switch (*s)
        {
        case '('//)
:
        case '['//]
:
        case '{'//}
:
            ++count;
            break;

        case //(
')':
        case //[
']':
        case //{
'}':
            --count;
            break;
        }
    }
    if (count > 0)
        count = -count;
    else
        count = 0;
    for (const char *s = vp; *s; ++s)
    {
        int c = *s;
        if (c < ' ' || c > '~')
        {
            const char *cp = strchr("\bb\ff\nn\rr\tt", c);
            if (cp)
                trace_printf("\\%c", cp[1]);
            else
                trace_printf("\\%03o", (unsigned char)c);
        }
        else
        {
            switch (c)
            {
            case '(':
            case '[':
            case '{':
                ++count;
                if (count <= 0)
                {
                    trace_printf("\\%03o", (unsigned char)c);
                    goto next;
                }
                break;

            case ')':
            case ']':
            case '}':
                --count;
                if (count < 0)
                {
                    trace_printf("\\%03o", (unsigned char)c);
                    goto next;
                }
                break;

            case '\\':
            case '"':
                trace_printf("\\");
                break;

            default:
                break;
            }
            trace_printf("%c", c);
        }
        next:;
    }
    trace_printf("\";\n");
}


void
trace_indent_reset(void)
{
#ifdef DEBUG
    (void)trace_pretest_result;
#endif
    depth = 0;
}
