/*
 * Copyright (c) 1989 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "hexdump.h"
#include "xalloc.h"
#include "c.h"
#include "nls.h"
#include "colors.h"

static void doskip(const char *, int, struct hexdump *);
static u_char *get(struct hexdump *);

enum _vflag vflag = FIRST;

static off_t address;			/* address/offset in stream */
static off_t eaddress;			/* end address */

static const char *color_cond(struct hexdump_pr *pr, unsigned char *bp, int bcnt)
{
	register struct list_head *p;
	register struct hexdump_clr *clr;
	off_t offt;
	int match;

	list_for_each(p, pr->colorlist) {
		clr = list_entry(p, struct hexdump_clr, colorlist);
		offt = clr->offt;
		match = 0;

		/* no offset or offset outside this print unit */
		if (offt < 0)
			offt = address;
		if (offt < address || offt + clr->range > address + bcnt)
			continue;

		/* match a string */
		if (clr->str) {
			if (pr->flags == F_ADDRESS) {
				/* TODO */
			}
			else if (!strncmp(clr->str, (char *)bp + offt
				- address, clr->range))
				match = 1;
		/* match a value */
		} else if (clr->val != -1) {
			int val = 0;
			/* addresses are not part of the input, so we can't
			 * compare with the contents of bp */
			if (pr->flags == F_ADDRESS) {
				if (clr->val == address)
					match = 1;
			} else {
				memcpy(&val, bp + offt - address, clr->range);
				if (val == clr->val)
					match = 1;
			}
		/* no conditions, only a color was specified */
		} else
			return clr->fmt;

		/* return the format string or check for another */
		if (match ^ clr->invert)
			return clr->fmt;
		continue;
	}

	/* no match */
	return NULL;
}

static inline void
print(struct hexdump_pr *pr, unsigned char *bp) {

	const char *color = NULL;

	if (pr->colorlist && (color = color_cond(pr, bp, pr->bcnt)))
		color_enable(color);

	switch(pr->flags) {
	case F_ADDRESS:
		printf(pr->fmt, address);
		break;
	case F_BPAD:
		printf(pr->fmt, "");
		break;
	case F_C:
		conv_c(pr, bp);
		break;
	case F_CHAR:
		printf(pr->fmt, *bp);
		break;
	case F_DBL:
	    {
		double dval;
		float fval;
		switch(pr->bcnt) {
		case 4:
			memmove(&fval, bp, sizeof(fval));
			printf(pr->fmt, fval);
			break;
		case 8:
			memmove(&dval, bp, sizeof(dval));
			printf(pr->fmt, dval);
			break;
		}
		break;
	    }
	case F_INT:
	    {
		short sval;	/* int16_t */
		int ival;	/* int32_t */
		long long Lval;	/* int64_t, int64_t */

		switch(pr->bcnt) {
		case 1:
			printf(pr->fmt, (unsigned long long) *bp);
			break;
		case 2:
			memmove(&sval, bp, sizeof(sval));
			printf(pr->fmt, (unsigned long long) sval);
			break;
		case 4:
			memmove(&ival, bp, sizeof(ival));
			printf(pr->fmt, (unsigned long long) ival);
			break;
		case 8:
			memmove(&Lval, bp, sizeof(Lval));
			printf(pr->fmt, Lval);
			break;
		}
		break;
	    }
	case F_P:
		printf(pr->fmt, isprint(*bp) ? *bp : '.');
		break;
	case F_STR:
		printf(pr->fmt, (char *)bp);
		break;
	case F_TEXT:
		printf("%s", pr->fmt);
		break;
	case F_U:
		conv_u(pr, bp);
		break;
	case F_UINT:
	    {
		unsigned short sval;	/* u_int16_t */
		unsigned int ival;	/* u_int32_t */
		unsigned long long Lval;/* u_int64_t, u_int64_t */

		switch(pr->bcnt) {
		case 1:
			printf(pr->fmt, (unsigned long long) *bp);
			break;
		case 2:
			memmove(&sval, bp, sizeof(sval));
			printf(pr->fmt, (unsigned long long) sval);
			break;
		case 4:
			memmove(&ival, bp, sizeof(ival));
			printf(pr->fmt, (unsigned long long) ival);
			break;
		case 8:
			memmove(&Lval, bp, sizeof(Lval));
			printf(pr->fmt, Lval);
			break;
		}
		break;
	    }
	}
	if (color) /* did we colorize something? */
		color_disable();
}

static void bpad(struct hexdump_pr *pr)
{
	static const char *spec = " -0+#";
	char *p1, *p2;

	/*
	 * remove all conversion flags; '-' is the only one valid
	 * with %s, and it's not useful here.
	 */
	pr->flags = F_BPAD;
	pr->cchar[0] = 's';
	pr->cchar[1] = 0;

	p1 = pr->fmt;
	while (*p1 != '%')
		++p1;

	p2 = ++p1;
	while (*p1 && strchr(spec, *p1))
		++p1;

	while ((*p2++ = *p1++))
		;
}

void display(struct hexdump *hex)
{
	register struct list_head *fs;
	register struct hexdump_fs *fss;
	register struct hexdump_fu *fu;
	register struct hexdump_pr *pr;
	register int cnt;
	register unsigned char *bp;
	off_t saveaddress;
	unsigned char savech = 0, *savebp;
	struct list_head *p, *q, *r;

	while ((bp = get(hex)) != NULL) {
		fs = &hex->fshead; savebp = bp; saveaddress = address;

		list_for_each(p, fs) {
			fss = list_entry(p, struct hexdump_fs, fslist);

			list_for_each(q, &fss->fulist) {
				fu = list_entry(q, struct hexdump_fu, fulist);

				if (fu->flags&F_IGNORE)
					break;

				cnt = fu->reps;

				while (cnt) {
					list_for_each(r, &fu->prlist) {
						pr = list_entry(r, struct hexdump_pr, prlist);

						if (eaddress && address >= eaddress
						    && !(pr->flags&(F_TEXT|F_BPAD)))
							bpad(pr);

						if (cnt == 1 && pr->nospace) {
							savech = *pr->nospace;
							*pr->nospace = '\0';
							print(pr, bp);
							*pr->nospace = savech;
						} else
							print(pr, bp);

						address += pr->bcnt;
						bp += pr->bcnt;
					}
					--cnt;
				}
			}
			bp = savebp;
			address = saveaddress;
		}
	}
	if (endfu) {
		/*
		 * if eaddress not set, error or file size was multiple of
		 * blocksize, and no partial block ever found.
		 */
		if (!eaddress) {
			if (!address)
				return;
			eaddress = address;
		}
		list_for_each (p, &endfu->prlist) {
			pr = list_entry(p, struct hexdump_pr, prlist);

			const char *color = NULL;
			if (colors_wanted() && pr->colorlist
			    && (color = color_cond(pr, bp, pr->bcnt))) {
				color_enable(color);
			}

			switch(pr->flags) {
			case F_ADDRESS:
				printf(pr->fmt, eaddress);
				break;
			case F_TEXT:
				printf("%s", pr->fmt);
				break;
			}
			if (color) /* did we highlight something? */
				color_disable();
		}
	}
}

static char **_argv;

static u_char *
get(struct hexdump *hex)
{
	static int ateof = 1;
	static u_char *curp, *savp;
	ssize_t n, need, nread;
	u_char *tmpp;

	if (!curp) {
		curp = xcalloc(1, hex->blocksize);
		savp = xcalloc(1, hex->blocksize);
	} else {
		tmpp = curp;
		curp = savp;
		savp = tmpp;
		address += hex->blocksize;
	}
	need = hex->blocksize, nread = 0;
	while (TRUE) {
		/*
		 * if read the right number of bytes, or at EOF for one file,
		 * and no other files are available, zero-pad the rest of the
		 * block and set the end flag.
		 */
		if (!hex->length || (ateof && !next(NULL, hex))) {
			if (need == hex->blocksize)
				goto retnul;
			if (!need && vflag != ALL &&
			    !memcmp(curp, savp, nread)) {
				if (vflag != DUP)
					printf("*\n");
				goto retnul;
			}
			if (need > 0)
				memset((char *)curp + nread, 0, need);
			eaddress = address + nread;
			return(curp);
		}
		if (fileno(stdin) == -1) {
			warnx(_("all input file arguments failed"));
			goto retnul;
		}
		n = fread((char *)curp + nread, sizeof(unsigned char),
		    hex->length == -1 ? need : min(hex->length, need), stdin);
		if (!n) {
			if (ferror(stdin))
				warn("%s", _argv[-1]);
			ateof = 1;
			continue;
		}
		ateof = 0;
		if (hex->length != -1)
			hex->length -= n;
		if (!(need -= n)) {
			if (vflag == ALL || vflag == FIRST ||
			    memcmp(curp, savp, hex->blocksize)) {
				if (vflag == DUP || vflag == FIRST)
					vflag = WAIT;
				return(curp);
			}
			if (vflag == WAIT)
				printf("*\n");
			vflag = DUP;
			address += hex->blocksize;
			need = hex->blocksize;
			nread = 0;
		}
		else
			nread += n;
	}
retnul:
	free (curp);
	free (savp);
	return NULL;
}

int next(char **argv, struct hexdump *hex)
{
	static int done;
	int statok;

	if (argv) {
		_argv = argv;
		return(1);
	}
	while (TRUE) {
		if (*_argv) {
			if (!(freopen(*_argv, "r", stdin))) {
				warn("%s", *_argv);
				hex->exitval = EXIT_FAILURE;
				++_argv;
				continue;
			}
			statok = done = 1;
		} else {
			if (done++)
				return(0);
			statok = 0;
		}
		if (hex->skip)
			doskip(statok ? *_argv : "stdin", statok, hex);
		if (*_argv)
			++_argv;
		if (!hex->skip)
			return(1);
	}
	/* NOTREACHED */
}

static void
doskip(const char *fname, int statok, struct hexdump *hex)
{
	struct stat sbuf;

	if (statok) {
		if (fstat(fileno(stdin), &sbuf))
		        err(EXIT_FAILURE, "%s", fname);
		if (S_ISREG(sbuf.st_mode) && hex->skip > sbuf.st_size) {
		  /* If size valid and skip >= size */
			hex->skip -= sbuf.st_size;
			address += sbuf.st_size;
			return;
		}
	}
	/* sbuf may be undefined here - do not test it */
	if (fseek(stdin, hex->skip, SEEK_SET))
	        err(EXIT_FAILURE, "%s", fname);
	address += hex->skip;
	hex->skip = 0;
}
