#include <u.h>
#include <libc.h>

/*
 * Access local time entries of zoneinfo files.
 * Formats 0 and 2 are supported, and 4-byte timestamps
 *
 * Copyright © 2008 M. Teichgräber
 * Contributed under the MIT license used in the rest of the distribution.
 */
#include "zoneinfo.h"

static
struct Zoneinfo
{
	int	timecnt;		/* # of transition times */
	int	typecnt;		/* # of local time types */
	int	charcnt;		/* # of characters of time zone abbreviation strings */

	uchar *ptime;
	uchar *ptype;
	uchar *ptt;
	uchar *pzone;
} z;

static uchar *tzdata;

static
uchar*
readtzfile(char *file)
{
	uchar *p;
	int fd;
	Dir *d;

	fd = open(file, OREAD);
	if (fd<0)
		return nil;
	d = dirfstat(fd);
	if (d==nil)
		return nil;
	p = malloc(d->length);
	if (p!=nil)
		readn(fd, p, d->length);
	free(d);
	close(fd);
	return p;
}
static char *zonefile;
void
tzfile(char *f)
{
	if (tzdata!=nil) {
		free(tzdata);
		tzdata = nil;
	}
	z.timecnt = 0;
	zonefile = f;
}

static
long
get4(uchar *p)
{
	return (p[0]<<24)+(p[1]<<16)+(p[2]<<8)+p[3];
}

enum {
	TTinfosz	= 4+1+1,
};

static
int
parsehead(void)
{
	uchar *p;
	int	ver;

	ver = tzdata[4];
	if (ver!=0)
	if (ver!='2')
		return -1;

	p = tzdata + 4 + 1 + 15;

	z.timecnt = get4(p+3*4);
	z.typecnt = get4(p+4*4);
	if (z.typecnt==0)
		return -1;
	z.charcnt = get4(p+5*4);
	z.ptime = p+6*4;
	z.ptype = z.ptime + z.timecnt*4;
	z.ptt = z.ptype + z.timecnt;
	z.pzone = z.ptt + z.typecnt*TTinfosz;
	return 0;
}

static
void
ttinfo(Tinfo *ti, int tti)
{
	uchar *p;
	int	i;

	i = z.ptype[tti];
	assert(i<z.typecnt);
	p = z.ptt + i*TTinfosz;
	ti->tzoff = get4(p);
	ti->dlflag = p[4];
	assert(p[5]<z.charcnt);
	ti->zone = (char*)z.pzone + p[5];
}

static
void
readtimezone(void)
{
	char *tmp;

	z.timecnt = 0;
	if(zonefile==nil) {
		if ((tmp=getenv("timezone"))!=nil) {
			tzdata = readtzfile(tmp);
			free(tmp);
			goto havedata;
		}
		zonefile = "/etc/localtime";
	}
	tzdata = readtzfile(zonefile);
	if (tzdata==nil)
		return;

havedata:
	if (strncmp("TZif", (char*)tzdata, 4)!=0)
		goto errfree;

	if (parsehead()==-1) {
	errfree:
		free(tzdata);
		tzdata = nil;
		z.timecnt = 0;
		return;
	}
}

static
tlong
gett4(uchar *p)
{
	long l;

	l = get4(p);
	if (l<0)
		return 0;
	return l;
}
int
zonetinfo(Tinfo *ti, int i)
{
	if (tzdata==nil)
		readtimezone();
	if (i<0 || i>=z.timecnt)
		return -1;
	ti->t = gett4(z.ptime + 4*i);
	ttinfo(ti, i);
	return i;
}

int
zonelookuptinfo(Tinfo *ti, tlong t)
{
	uchar *p;
	int	i;
	tlong	oldtt, tt;

	if (tzdata==nil)
		readtimezone();
	oldtt = 0;
	p = z.ptime;
	for (i=0; i<z.timecnt; i++) {
		tt = gett4(p);
		if (t<tt)
			break;
		oldtt = tt;
		p += 4;
	}
	if (i>0) {
		ttinfo(ti, i-1);
		ti->t = oldtt;
//		fprint(2, "t:%ld off:%d dflag:%d %s\n", (long)ti->t, ti->tzoff, ti->dlflag, ti->zone);
		return i-1;
	}
	return -1;
}

void
zonedump(int fd)
{
	int	i;
	uchar *p;
	tlong t;
	Tinfo ti;

	if (tzdata==nil)
		readtimezone();
	p = z.ptime;
	for (i=0; i<z.timecnt; i++) {
		t = gett4(p);
		ttinfo(&ti, i);
		fprint(fd, "%ld\t%d\t%d\t%s\n", (long)t, ti.tzoff, ti.dlflag, ti.zone);
		p += 4;
	}
}
