#include "cache.h"
/*
 * convert.c - convert a file when checking it out and checking it in.
 *
 * This should use the pathname to decide on whether it wants to do some
 * more interesting conversions (automatic gzip/unzip, general format
 * conversions etc etc), but by default it just does automatic CRLF<->LF
 * translation when the "auto_crlf" option is set.
 */

struct text_stat {
	/* CR, LF and CRLF counts */
	unsigned cr, lf, crlf;

	/* These are just approximations! */
	unsigned printable, nonprintable;
};

static void gather_stats(const char *buf, unsigned long size, struct text_stat *stats)
{
	unsigned long i;

	memset(stats, 0, sizeof(*stats));

	for (i = 0; i < size; i++) {
		unsigned char c = buf[i];
		if (c == '\r') {
			stats->cr++;
			if (i+1 < size && buf[i+1] == '\n')
				stats->crlf++;
			continue;
		}
		if (c == '\n') {
			stats->lf++;
			continue;
		}
		if (c == 127)
			/* DEL */
			stats->nonprintable++;
		else if (c < 32) {
			switch (c) {
				/* BS, HT, ESC and FF */
			case '\b': case '\t': case '\033': case '\014':
				stats->printable++;
				break;
			default:
				stats->nonprintable++;
			}
		}
		else
			stats->printable++;
	}
}

/*
 * The same heuristics as diff.c::mmfile_is_binary()
 */
static int is_binary(unsigned long size, struct text_stat *stats)
{

	if ((stats->printable >> 7) < stats->nonprintable)
		return 1;
	/*
	 * Other heuristics? Average line length might be relevant,
	 * as might LF vs CR vs CRLF counts..
	 *
	 * NOTE! It might be normal to have a low ratio of CRLF to LF
	 * (somebody starts with a LF-only file and edits it with an editor
	 * that adds CRLF only to lines that are added..). But do  we
	 * want to support CR-only? Probably not.
	 */
	return 0;
}

int convert_to_git(const char *path, char **bufp, unsigned long *sizep)
{
	char *buffer, *nbuf;
	unsigned long size, nsize;
	struct text_stat stats;

	/*
	 * FIXME! Other pluggable conversions should go here,
	 * based on filename patterns. Right now we just do the
	 * stupid auto-CRLF one.
	 */
	if (!auto_crlf)
		return 0;

	size = *sizep;
	if (!size)
		return 0;
	buffer = *bufp;

	gather_stats(buffer, size, &stats);

	/* No CR? Nothing to convert, regardless. */
	if (!stats.cr)
		return 0;

	/*
	 * We're currently not going to even try to convert stuff
	 * that has bare CR characters. Does anybody do that crazy
	 * stuff?
	 */
	if (stats.cr != stats.crlf)
		return 0;

	/*
	 * And add some heuristics for binary vs text, of course...
	 */
	if (is_binary(size, &stats))
		return 0;

	/*
	 * Ok, allocate a new buffer, fill it in, and return true
	 * to let the caller know that we switched buffers on it.
	 */
	nsize = size - stats.crlf;
	nbuf = xmalloc(nsize);
	*bufp = nbuf;
	*sizep = nsize;
	do {
		unsigned char c = *buffer++;
		if (c != '\r')
			*nbuf++ = c;
	} while (--size);

	return 1;
}

int convert_to_working_tree(const char *path, char **bufp, unsigned long *sizep)
{
	char *buffer, *nbuf;
	unsigned long size, nsize;
	struct text_stat stats;
	unsigned char last;

	/*
	 * FIXME! Other pluggable conversions should go here,
	 * based on filename patterns. Right now we just do the
	 * stupid auto-CRLF one.
	 */
	if (auto_crlf <= 0)
		return 0;

	size = *sizep;
	if (!size)
		return 0;
	buffer = *bufp;

	gather_stats(buffer, size, &stats);

	/* No LF? Nothing to convert, regardless. */
	if (!stats.lf)
		return 0;

	/* Was it already in CRLF format? */
	if (stats.lf == stats.crlf)
		return 0;

	/* If we have any bare CR characters, we're not going to touch it */
	if (stats.cr != stats.crlf)
		return 0;

	if (is_binary(size, &stats))
		return 0;

	/*
	 * Ok, allocate a new buffer, fill it in, and return true
	 * to let the caller know that we switched buffers on it.
	 */
	nsize = size + stats.lf - stats.crlf;
	nbuf = xmalloc(nsize);
	*bufp = nbuf;
	*sizep = nsize;
	last = 0;
	do {
		unsigned char c = *buffer++;
		if (c == '\n' && last != '\r')
			*nbuf++ = '\r';
		*nbuf++ = c;
		last = c;
	} while (--size);

	return 1;
}
