/*
 * fhist - file history and comparison tools
 * Copyright (C) 1998, 2000, 2002, 2008, 2010 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 <common/ac/errno.h>
#include <common/ac/stddef.h>
#include <common/ac/stdio.h>
#include <common/ac/string.h>
#include <common/ac/stdarg.h>
#include <libexplain/fflush.h>
#include <libexplain/malloc.h>
#include <libexplain/program_name.h>

#include <common/error.h>
#include <common/str.h>
#include <common/trace.h>


#define INDENT 2
#define PAGE_WIDTH 79

typedef struct known_ty known_ty;
struct known_ty
{
        string_ty       *filename;
        int             flag;
        int             *flag_p;
        known_ty        *next;
};

static  string_ty       *file_name;
static  int             line_number;
static  int             page_width;
static  known_ty        *known;


static string_ty *
base_name(const char *file)
{
        const char      *cp1;
        const char      *cp2;

        cp1 = strrchr(file, '/');
        if (cp1)
                ++cp1;
        else
                cp1 = file;
        cp2 = strrchr(cp1, '.');
        if (!cp2)
                cp2 = cp1 + strlen(cp1);
        if (cp2 > cp1 + 6)
                return str_n_from_c(cp1, 6);
        return str_n_from_c(cp1, cp2 - cp1);
}


int
trace_pretest(const char *file, int *result)
{
        string_ty       *s;
        known_ty        *kp;
        int             err;

        err = errno;
        s = base_name(file);
        for (kp = known; kp; kp = kp->next)
        {
                if (str_equal(s, kp->filename))
                {
                        str_free(s);
                        break;
                }
        }
        if (!kp)
        {
                kp = (known_ty *)explain_malloc_or_die(sizeof(known_ty));
                kp->filename = s;
                kp->next = known;
                kp->flag = 2; /* disabled */
                known = kp;
        }
        kp->flag_p = result;
        *result = kp->flag;
        errno = err;
        return *result;
}


void
trace_where(const char *file, int line)
{
        string_ty       *s;
        int             err;

        /*
         * take new name fist, because will probably be same as last
         * thus saving a free and a malloc (which are slow)
         */
        err = errno;
        s = base_name(file);
        if (file_name)
                str_free(file_name);
        file_name = s;
        line_number = line;
        errno = err;
}


static void
trace_putchar(int c)
{
        static int      depth;
        static char     buffer[PAGE_WIDTH + 2];
        static char     *cp;
        static int      in_col;
        static int      out_col;
        const char      *progname;

        progname = explain_program_name_get();
        if (!page_width)
        {
                page_width = PAGE_WIDTH - 24;
                if (page_width < 8)
                        page_width = 8;
        }
        if (!cp)
        {
                strcpy(buffer, progname);
                cp = buffer + strlen(buffer);
                if (cp > buffer + 6)
                        cp = buffer + 6;
                *cp++ = ':';
                *cp++ = '\t';
                strcpy(cp, file_name->str_text);
                cp += file_name->str_length;
                *cp++ = ':';
                *cp++ = '\t';
                sprintf(cp, "%d:\t", line_number);
                cp += strlen(cp);
                in_col = 0;
                out_col = 0;
        }
        switch (c)
        {
        case '\n':
                *cp++ = '\n';
                *cp = 0;
                fflush(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, ...)
{
        string_ty       *buffer;
        va_list         ap;
        int             err;

        err = errno;
        va_start(ap, s);
        buffer = str_vformat(s, ap);
        va_end(ap);
        for (s = buffer->str_text; *s; ++s)
                trace_putchar(*s);
        str_free(buffer);
        errno = err;
}


void
trace_enable(const char *file)
{
        string_ty       *s;
        known_ty        *kp;

        s = base_name(file);
        for (kp = known; kp; kp = kp->next)
        {
                if (str_equal(s, kp->filename))
                {
                        str_free(s);
                        break;
                }
        }
        if (!kp)
        {
                kp = (known_ty *)explain_malloc_or_die(sizeof(known_ty));
                kp->filename = s;
                kp->flag_p = 0;
                kp->next = known;
                known = kp;
        }
        kp->flag = 3; /* enabled */
        if (kp->flag_p)
                *kp->flag_p = kp->flag;

        /*
         * silence a warning
         */
#ifdef DEBUG
        trace_pretest_result = 0;
#endif
}


void
trace_char_real(const char *name, const char *vp)
{
        trace_printf("%s = '", name);
        if (*vp < ' ' || *vp > '~' || strchr("(){}[]", *vp))
        {
                char    *s;

                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(const char *name, const unsigned char *vp)
{
        trace_printf("%s = '", name);
        if (*vp < ' ' || *vp > '~' || strchr("(){}[]", *vp))
        {
                char    *s;

                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(const char *name, const int *vp)
{
        trace_printf("%s = %d;\n", name, *vp);
}


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


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


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


void
trace_pointer_real(const char *name, const void *vptrptr)
{
        const void *const *ptr_ptr = vptrptr;
        const void      *ptr;

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


void
trace_short_real(const 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)
{
        const char      *s;
        long            count;

        trace_printf("%s = ", name);
        if (!vp)
        {
                trace_printf("NULL;\n");
                return;
        }
        trace_printf("\"");
        count = 0;
        for (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 (s = vp; *s; ++s)
        {
                int     c;

                c = *s;
                if (c < ' ' || c > '~')
                {
                        char    *cp;

                        cp = strchr("\bb\ff\nn\rr\tt", c);
                        if (cp)
                                trace_printf("\\%c", cp[1]);
                        else
                        {
                                escape:
                                trace_printf("\\%03o", (unsigned char)c);
                        }
                }
                else
                {
                        switch (c)
                        {
                        case '('/*)*/:
                        case '['/*]*/:
                        case '{'/*}*/:
                                ++count;
                                if (count <= 0)
                                        goto escape;
                                break;

                        case /*(*/')':
                        case /*[*/']':
                        case /*{*/'}':
                                --count;
                                if (count < 0)
                                        goto escape;
                                break;

                        case '\\':
                        case '"':
                                trace_printf("\\");
                                break;
                        }
                        trace_printf("%c", c);
                }
        }
        trace_printf("\";\n");
}
