/*
 * extended printf functions and specifiers
 *
 * Copyright (C) 2010 by James Maki
 *
 * Author: James Maki <jmaki@multitech.com>
 *
 * 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 2 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#define _GNU_SOURCE

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <alloca.h>
#include "xprintf.h"

#include "log.h"

#if HAVE_REGISTER_PRINTF_SPECIFIER
int print_buffer_inspect_arginfo(const struct printf_info *info, size_t n, int *argtypes, int *size)
#else
int print_buffer_inspect_arginfo(const struct printf_info *info, size_t n, int *argtypes)
#endif
{
	if (n > 0) {
		argtypes[0] = PA_POINTER;
#if HAVE_REGISTER_PRINTF_SPECIFIER
		size[0] = sizeof(void *);
#endif
	}
	return 1;
}

static const char *__char_inspect[] = {
	"\\x00",	"\\x01",	"\\x02",	"\\x03",
	"\\x04",	"\\x05",	"\\x06",	"\\a",
	"\\b",		"\\t",		"\\n",		"\\v",
	"\\f",		"\\r",		"\\x0E",	"\\x0F",
	"\\x10",	"\\x11",	"\\x12",	"\\x13",
	"\\x14",	"\\x15",	"\\x16",	"\\x17",
	"\\x18",	"\\x19",	"\\x1A",	"\\x1B",
	"\\x1C",	"\\x1D",	"\\x1E",	"\\x1F",
	" ",		"!",		"\\\"",		"#",
	"$",		"%",		"&",		"'",
	"(",		")",		"*",		"+",
	",",		"-",		".",		"/",
	"0",		"1",		"2",		"3",
	"4",		"5",		"6",		"7",
	"8",		"9",		":",		";",
	"<",		"=",		">",		"?",
	"@",		"A",		"B",		"C",
	"D",		"E",		"F",		"G",
	"H",		"I",		"J",		"K",
	"L",		"M",		"N",		"O",
	"P",		"Q",		"R",		"S",
	"T",		"U",		"V",		"W",
	"X",		"Y",		"Z",		"[",
	"\\\\",		"]",		"^",		"_",
	"`",		"a",		"b",		"c",
	"d",		"e",		"f",		"g",
	"h",		"i",		"j",		"k",
	"l",		"m",		"n",		"o",
	"p",		"q",		"r",		"s",
	"t",		"u",		"v",		"w",
	"x",		"y",		"z",		"{",
	"|",		"}",		"~",		"\\x7F",
	"\\x80",	"\\x81",	"\\x82",	"\\x83",
	"\\x84",	"\\x85",	"\\x86",	"\\x87",
	"\\x88",	"\\x89",	"\\x8A",	"\\x8B",
	"\\x8C",	"\\x8D",	"\\x8E",	"\\x8F",
	"\\x90",	"\\x91",	"\\x92",	"\\x93",
	"\\x94",	"\\x95",	"\\x96",	"\\x97",
	"\\x98",	"\\x99",	"\\x9A",	"\\x9B",
	"\\x9C",	"\\x9D",	"\\x9E",	"\\x9F",
	"\\xA0",	"\\xA1",	"\\xA2",	"\\xA3",
	"\\xA4",	"\\xA5",	"\\xA6",	"\\xA7",
	"\\xA8",	"\\xA9",	"\\xAA",	"\\xAB",
	"\\xAC",	"\\xAD",	"\\xAE",	"\\xAF",
	"\\xB0",	"\\xB1",	"\\xB2",	"\\xB3",
	"\\xB4",	"\\xB5",	"\\xB6",	"\\xB7",
	"\\xB8",	"\\xB9",	"\\xBA",	"\\xBB",
	"\\xBC",	"\\xBD",	"\\xBE",	"\\xBF",
	"\\xC0",	"\\xC1",	"\\xC2",	"\\xC3",
	"\\xC4",	"\\xC5",	"\\xC6",	"\\xC7",
	"\\xC8",	"\\xC9",	"\\xCA",	"\\xCB",
	"\\xCC",	"\\xCD",	"\\xCE",	"\\xCF",
	"\\xD0",	"\\xD1",	"\\xD2",	"\\xD3",
	"\\xD4",	"\\xD5",	"\\xD6",	"\\xD7",
	"\\xD8",	"\\xD9",	"\\xDA",	"\\xDB",
	"\\xDC",	"\\xDD",	"\\xDE",	"\\xDF",
	"\\xE0",	"\\xE1",	"\\xE2",	"\\xE3",
	"\\xE4",	"\\xE5",	"\\xE6",	"\\xE7",
	"\\xE8",	"\\xE9",	"\\xEA",	"\\xEB",
	"\\xEC",	"\\xED",	"\\xEE",	"\\xEF",
	"\\xF0",	"\\xF1",	"\\xF2",	"\\xF3",
	"\\xF4",	"\\xF5",	"\\xF6",	"\\xF7",
	"\\xF8",	"\\xF9",	"\\xFA",	"\\xFB",
	"\\xFC",	"\\xFD",	"\\xFE",	"\\xFF",
};

int print_buffer_inspect(FILE *stream, const struct printf_info *info, const void *const *args)
{
	int i;
	int total;
	char *arg;
	char *buf;
	int prec = info->prec;

	arg = (char *) *((const char **) (args[0]));

	if (prec < 0) {
		prec = strlen(arg);
	}

	total = 0;
	for (i = 0; i < prec; i++) {
		total += strlen(__char_inspect[((unsigned char *) arg)[i]]);
	}

	buf = alloca(total + 1);
	if (!buf) {
		return -1;
	}

	*buf = '\0';
	for (i = 0; i < prec; i++) {
		strcat(buf, __char_inspect[((unsigned char *) arg)[i]]);
	}

	total = fprintf(stream, "%*s", (info->left ? -info->width : info->width), buf);

	return total;
}

#if HAVE_REGISTER_PRINTF_SPECIFIER
int print_buffer_bin_arginfo(const struct printf_info *info, size_t n, int *argtypes, int *size)
#else
int print_buffer_bin_arginfo(const struct printf_info *info, size_t n, int *argtypes)
#endif
{
	if (n > 0) {
		argtypes[0] = PA_POINTER;
#if HAVE_REGISTER_PRINTF_SPECIFIER
		size[0] = sizeof(void *);
#endif
	}
	return 1;
}

int print_buffer_bin(FILE *stream, const struct printf_info *info, const void *const *args)
{
	int i;
	int j;
	int total;
	char *arg;
	unsigned char c;
	int prec = info->prec;
	char *buf;

	arg = (char *) *((const char **) (args[0]));

	if (prec < 0) {
		prec = strlen(arg);
	}

	buf = alloca(prec * 8 + 1);
	if (!buf) {
		return -1;
	}

	total = 0;
	for (i = 0; i < prec; i++) {
		c = ((unsigned char *) arg)[i];
		for (j = 7; j >= 0; j--) {
			buf[total++] = (c & (1 << j)) ? '1' : '0';
		}
	}
	if (total) {
		buf[total] = '\0';
	}

	total = fprintf(stream, "%*s", (info->left ? -info->width : info->width), buf);

	return total;
}

int xprintf_init()
{
#if HAVE_REGISTER_PRINTF_SPECIFIER
	register_printf_specifier(XPRINTF_INSPECT_SPEC, print_buffer_inspect,
					print_buffer_inspect_arginfo);
	register_printf_specifier(XPRINTF_BIN_SPEC, print_buffer_bin,
					print_buffer_bin_arginfo);
#else
	register_printf_function(XPRINTF_INSPECT_SPEC, print_buffer_inspect,
					print_buffer_inspect_arginfo);
	register_printf_function(XPRINTF_BIN_SPEC, print_buffer_bin,
					print_buffer_bin_arginfo);
#endif

	return 0;
}