// This file is part of Deark.
// Copyright (C) 2016 Jason Summers
// See the file COPYING for terms of use.

// PaintShop Pro Browser Cache (JBF) (pspbrwse.jbf)

// This module was developed with the help of information from
// jbfinspect.c (https://github.com/0x09/jbfinspect), which says:
//     "The author disclaims all copyright on this code."

#include <deark-config.h>
#include <deark-private.h>
#include <deark-fmtutil.h>
DE_DECLARE_MODULE(de_module_jbf);

struct page_ctx {
	de_finfo *fi;
	de_ucstring *fname;
	const char *thumbnail_ext;
};

typedef struct localctx_struct {
	de_encoding input_encoding;
	UI ver_major;
	UI ver_minor;
	UI ver_combined;
	i64 image_count;
} lctx;

static const u8 v1pal[256*3] = {
	0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0xfe,0x00,0x00,0x00,0xfe,0xff,0xff,0x00,
	0xff,0x00,0xff,0x00,0xff,0xff,0x0f,0x0f,0x0f,0x17,0x17,0x17,0x1f,0x1f,0x1f,0x27,0x27,0x27,
	0x38,0x38,0x38,0x40,0x40,0x40,0x48,0x48,0x48,0x4f,0x4f,0x4f,0x60,0x60,0x60,0x68,0x68,0x68,
	0x70,0x70,0x70,0x80,0x80,0x80,0x97,0x97,0x97,0xa0,0xa0,0xa0,0xb0,0xb0,0xb0,0xb8,0xb8,0xb8,
	0xbf,0xbf,0xbf,0xc8,0xc8,0xc8,0xd9,0xd9,0xd9,0xe0,0xe0,0xe0,0xe8,0xe8,0xe8,0xf0,0xf0,0xf0,
	0xc0,0x00,0x00,0x17,0x06,0x06,0x27,0x05,0x06,0x40,0x0b,0x0b,0x50,0x0f,0x0f,0x68,0x17,0x17,
	0x80,0x17,0x17,0x98,0x1b,0x1b,0xa0,0x1f,0x20,0xb8,0x23,0x24,0xc8,0x27,0x28,0xe0,0x2b,0x2b,
	0xf1,0x2f,0x2f,0xff,0x40,0x40,0xfe,0x50,0x50,0xff,0x60,0x60,0xff,0x70,0x6f,0xff,0x80,0x7f,
	0xff,0x98,0x98,0xff,0xa0,0xa0,0xff,0xb0,0xb0,0xfe,0xc0,0xc0,0xff,0xd0,0xd0,0xff,0xe0,0xe0,
	0xff,0xf0,0xf0,0x00,0xc0,0x00,0x06,0x17,0x05,0x06,0x27,0x05,0x0a,0x40,0x0b,0x0f,0x50,0x0f,
	0x17,0x67,0x17,0x17,0x7f,0x17,0x1b,0x97,0x1b,0x1f,0xa0,0x20,0x24,0xb7,0x24,0x27,0xc8,0x27,
	0x2c,0xe0,0x2b,0x30,0xf0,0x2f,0x40,0xff,0x40,0x50,0xff,0x50,0x60,0xff,0x60,0x70,0xff,0x70,
	0x80,0xff,0x80,0x98,0xff,0x98,0x9f,0xff,0xa0,0xb0,0xff,0xb0,0xc0,0xfe,0xc0,0xd0,0xfe,0xd1,
	0xe0,0xff,0xe1,0xf0,0xff,0xf0,0x00,0x00,0xc0,0x06,0x06,0x17,0x05,0x06,0x28,0x0a,0x0b,0x3f,
	0x0f,0x0e,0x4f,0x17,0x17,0x68,0x17,0x17,0x80,0x1c,0x1b,0x98,0x20,0x20,0xa0,0x23,0x24,0xb8,
	0x27,0x28,0xc8,0x2b,0x2b,0xe0,0x2f,0x2f,0xf0,0x40,0x40,0xff,0x50,0x50,0xff,0x60,0x5f,0xff,
	0x6f,0x70,0xff,0x80,0x80,0xff,0x97,0x97,0xfe,0x9f,0xa0,0xff,0xb0,0xaf,0xff,0xc0,0xc0,0xff,
	0xd0,0xd0,0xff,0xe0,0xe0,0xfe,0xf0,0xf0,0xff,0xc0,0xc1,0x00,0x17,0x17,0x06,0x28,0x27,0x05,
	0x40,0x40,0x0b,0x4f,0x50,0x0f,0x68,0x67,0x17,0x80,0x80,0x17,0x97,0x98,0x1b,0xa0,0xa0,0x1f,
	0xb8,0xb8,0x24,0xc8,0xc8,0x27,0xe1,0xe0,0x2b,0xf0,0xf0,0x30,0xff,0xff,0x3f,0xff,0xff,0x50,
	0xfe,0xff,0x60,0xff,0xff,0x6f,0xff,0xff,0x80,0xff,0xff,0x98,0xff,0xfe,0xa0,0xfe,0xfe,0xb1,
	0xff,0xff,0xc0,0xff,0xff,0xd0,0xfe,0xff,0xe0,0xff,0xff,0xf0,0xc0,0x00,0xc0,0x17,0x05,0x17,
	0x27,0x05,0x27,0x40,0x0a,0x3f,0x50,0x0f,0x50,0x68,0x17,0x68,0x80,0x17,0x7f,0x98,0x1b,0x98,
	0xa0,0x1f,0x9f,0xb8,0x23,0xb8,0xc9,0x27,0xc8,0xe0,0x2b,0xe1,0xf0,0x2f,0xf1,0xff,0x40,0xfe,
	0xff,0x50,0xfe,0xff,0x5f,0xff,0xff,0x70,0xff,0xff,0x7f,0xff,0xfe,0x98,0xff,0xfe,0xa0,0xff,
	0xff,0xb0,0xff,0xff,0xc0,0xff,0xff,0xcf,0xfe,0xff,0xdf,0xff,0xff,0xf0,0xff,0x00,0xc0,0xc0,
	0x06,0x27,0x27,0x0a,0x40,0x40,0x0f,0x4f,0x50,0x18,0x68,0x68,0x17,0x80,0x80,0x1b,0x98,0x98,
	0x1f,0xa0,0xa0,0x23,0xb8,0xb8,0x27,0xc8,0xc8,0x2c,0xe0,0xdf,0x30,0xf0,0xf0,0x40,0xff,0xff,
	0x4f,0xff,0xff,0x60,0xff,0xff,0x70,0xff,0xfe,0x80,0xff,0xfe,0x97,0xff,0xfe,0xa0,0xff,0xff,
	0xaf,0xff,0xff,0xc1,0xff,0xff,0xcf,0xff,0xff,0xf1,0xff,0xff,0x17,0x0f,0x05,0x27,0x17,0x05,
	0x40,0x1f,0x0a,0x50,0x27,0x0f,0x67,0x38,0x17,0x80,0x40,0x17,0x98,0x48,0x1b,0xa0,0x50,0x1f,
	0xb8,0x60,0x23,0xc8,0x68,0x28,0xe0,0x70,0x2b,0xf0,0x80,0x2f,0xf8,0x88,0x40,0xf4,0x98,0x50,
	0xf4,0x97,0x60,0xf8,0xa0,0x70,0xf8,0xb0,0x80,0xf8,0xb8,0x98,0xf9,0xbe,0xa0,0xfa,0xc8,0xb1,
	0xff,0xd9,0xc0,0xff,0xe0,0xd0,0xff,0xe8,0xe0,0xff,0xf0,0xf0,0x28,0x17,0x0f,0x40,0x23,0x17,
	0x4f,0x2f,0x1f,0x67,0x40,0x28,0x7f,0x48,0x30,0x98,0x54,0x38,0x9f,0x60,0x40,0xb8,0x6c,0x48,
	0xc8,0x80,0x50,0xd9,0x84,0x5c,0xe0,0x97,0x68,0xdf,0x9c,0x73,0xe4,0xa8,0x80,0xe8,0xb7,0x97,
	0xe9,0xc0,0x98,0xef,0xcc,0xa4,0xef,0xd8,0xb0,0xf8,0xe4,0xbc,0xf9,0xf0,0xc8,0xf8,0xf8,0xd4,
	0xff,0xf4,0xe1,0xff,0xf7,0xf0,0x50,0x50,0x7f,0x5f,0x5f,0x88,0x68,0x68,0x97,0x70,0x6f,0x98,
	0x80,0x80,0xa0,0x88,0x88,0xb0,0x97,0x98,0xbe,0x98,0x98,0xc8,0xa0,0xa0,0xd9,0xb0,0xb0,0xe0,
	0xb8,0xb8,0xe8,0x50,0x80,0x4f,0x5f,0x87,0x60,0x67,0x98,0x68,0x6f,0x98,0x70,0x7f,0xa0,0x80,
	0x88,0xb0,0x87,0x98,0xbe,0x98,0x98,0xc8,0x98,0xa0,0xd8,0x9f,0xb0,0xe1,0xb0,0xb8,0xe8,0xb8,
	0x00,0x7f,0xf0,0x00,0xf0,0x80,0x7f,0x00,0xf0,0x78,0xf0,0x00,0xf1,0x80,0x00,0xf0,0x00,0x80,
	0xc1,0x00,0x37,0xa8,0x90,0x80,0x60,0x68,0x48,0x88,0x78,0x60 };

static int do_read_header(deark *c, lctx *d, i64 pos)
{
	int retval = 0;

	de_dbg(c, "header at %"I64_FMT, pos);
	de_dbg_indent(c, 1);

	pos += 15;
	d->ver_major = (UI)de_getu16be_p(&pos);
	d->ver_minor = (UI)de_getu16be_p(&pos);
	d->ver_combined = (d->ver_major<<16) | d->ver_minor;
	de_dbg(c, "format version: %u.%u", d->ver_major, d->ver_minor);

	if(d->ver_major<1 || d->ver_major>2) {
		de_err(c, "Unsupported JBF format version: %u.%u", d->ver_major, d->ver_minor);
		goto done;
	}
	if(d->ver_major==1 && (d->ver_minor==2 || d->ver_minor>3)) {
		de_warn(c, "Unrecognized JBF format version (%u.%u). File may not be "
			"decoded correctly.", d->ver_major, d->ver_minor);
	}

	d->image_count = de_getu32le(pos);
	de_dbg(c, "image count: %d", (int)d->image_count);
	//pos+=4;
	if(!de_good_image_count(c, d->image_count)) goto done;

	retval = 1;
done:
	de_dbg_indent(c, -1);
	return retval;
}

static const char *get_type_name(UI filetype_code)
{
	const char *nm = "unknown";

	switch(filetype_code) {
	// There are many more PSP file types. These are just some common ones.
	case 0x00: nm="none"; break;
	case 0x01: nm="BMP"; break;
	case 0x0a: nm="GIF"; break;
	case 0x11: nm="JPEG"; break;
	case 0x18: nm="PCX"; break;
	case 0x1c: nm="PNG"; break;
	case 0x1f: nm="PSP"; break;
	case 0x23: nm="TGA"; break;
	case 0x24: nm="TIFF"; break;
	}
	return nm;
}

static int read_filename(deark *c, lctx *d, struct page_ctx *pg, i64 pos1, i64 *bytes_consumed)
{
	int retval = 0;
	i64 pos = pos1;
	de_ucstring *fname_orig = NULL;

	fname_orig = ucstring_create(c);

	if(d->ver_combined>=0x010001) { // v1.1+
		i64 fnlen;
		fnlen = de_getu32le_p(&pos);
		de_dbg(c, "original filename len: %d", (int)fnlen);
		if(fnlen>1000) {
			de_err(c, "Bad filename length");
			goto done;
		}

		dbuf_read_to_ucstring(c->infile, pos, fnlen, fname_orig, 0, d->input_encoding);
		pos += fnlen;
	}
	else { // v1.0
		// File always has 13 bytes reserved for the filename.
		// The name is up to 12 bytes long, terminated by 0x00.
		dbuf_read_to_ucstring(c->infile, pos, 12, fname_orig, DE_CONVFLAG_STOP_AT_NUL,
			d->input_encoding);
		pos += 13;
	}

	de_dbg(c, "original filename: \"%s\"", ucstring_getpsz(fname_orig));

	if(c->filenames_from_file) {
		pg->fname = ucstring_clone(fname_orig);
		ucstring_append_sz(pg->fname, ".thumb.", DE_ENCODING_LATIN1);
		if(d->ver_major>=2)
			ucstring_append_sz(pg->fname, "jpg", DE_ENCODING_LATIN1);
		else
			ucstring_append_sz(pg->fname, "bmp", DE_ENCODING_LATIN1);
		de_finfo_set_name_from_ucstring(c, pg->fi, pg->fname, 0);
		pg->fi->original_filename_flag = 1;
	}
	else {
		if(d->ver_major>=2)
			pg->thumbnail_ext = "jpg";
		else
			pg->thumbnail_ext = "bmp";
	}

	retval = 1;
done:
	ucstring_destroy(fname_orig);
	*bytes_consumed = pos - pos1;
	return retval;
}

static void read_FILETIME(deark *c, lctx *d, struct page_ctx *pg, i64 pos)
{
	i64 ft;
	char timestamp_buf[64];

	ft = de_geti64le(pos);
	de_FILETIME_to_timestamp(ft, &pg->fi->timestamp[DE_TIMESTAMPIDX_MODIFY], 0x1);
	de_timestamp_to_string(&pg->fi->timestamp[DE_TIMESTAMPIDX_MODIFY], timestamp_buf, sizeof(timestamp_buf), 0);
	de_dbg(c, "mod time: %s", timestamp_buf);
}

static void read_unix_time(deark *c, lctx *d, struct page_ctx *pg, i64 pos)
{
	i64 ut;
	char timestamp_buf[64];

	ut = de_geti32le(pos);
	de_unix_time_to_timestamp(ut, &pg->fi->timestamp[DE_TIMESTAMPIDX_MODIFY], 0x1);
	de_timestamp_to_string(&pg->fi->timestamp[DE_TIMESTAMPIDX_MODIFY], timestamp_buf, sizeof(timestamp_buf), 0);
	de_dbg(c, "mod time: %s", timestamp_buf);
}

static int read_bitmap_v1(deark *c, lctx *d, struct page_ctx *pg, i64 pos1, i64 *bytes_consumed)
{
	struct de_bmpinfo bi;
	int retval = 0;
	dbuf *outf = NULL;
	i64 pos = pos1;
	i64 k;
	i64 count;
	i64 dec_bytes = 0;

	de_dbg(c, "bitmap at %"I64_FMT, pos);
	de_dbg_indent(c, 1);

	if(!fmtutil_get_bmpinfo(c, c->infile, &bi, pos, c->infile->len-pos, 0)) {
		de_err(c, "Invalid bitmap");
		goto done;
	}

	if(bi.infohdrsize != 40) {
		de_err(c, "Unexpected BMP format");
		goto done;
	}

	outf = dbuf_create_output_file(c, pg->thumbnail_ext, pg->fi, 0);
	dbuf_enable_wbuffer(outf);

	// Manufacture a BMP fileheader
	fmtutil_generate_bmpfileheader(c, outf, &bi, 0);

	// Copy the BITMAPINFOHEADER
	dbuf_copy(c->infile, pos, bi.infohdrsize, outf);

	// Write the standard palette
	for(k=0; k<256; k++) {
		dbuf_writebyte(outf, v1pal[k*3+2]);
		dbuf_writebyte(outf, v1pal[k*3+1]);
		dbuf_writebyte(outf, v1pal[k*3]);
		dbuf_writebyte(outf, 0);
	}

	pos += bi.infohdrsize;

	// Decompress the image
	while(1) {
		u8 b0, b1;

		// Stop if we reach the end of the input file.
		if(pos >= c->infile->len) break;

		// Stop if we decompressed the expected number of bytes
		if(dec_bytes >= bi.foreground_size) break;

		b0 = de_getbyte_p(&pos);

		if(d->ver_minor>=3) {
			if(b0>0x80) { // a compressed run
				count = (i64)(b0-0x80);
				b1 = de_getbyte_p(&pos);
				dbuf_write_run(outf, b1, count);
				dec_bytes += count;
			}
			else { // uncompressed run
				count = (i64)b0;
				dbuf_copy(c->infile, pos, count, outf);
				pos += count;
				dec_bytes += count;
			}
		}
		else {
			if(b0>0xc0) { // a compressed run
				count = (i64)(b0-0xc0);
				b1 = de_getbyte_p(&pos);
				dbuf_write_run(outf, b1, count);
				dec_bytes += count;
			}
			else { // literal byte
				count = 1;
				dbuf_writebyte(outf, b0);
				dec_bytes += count;
			}
		}
	}

	retval = 1;
done:
	dbuf_close(outf);
	*bytes_consumed = pos - pos1;
	de_dbg_indent(c, -1);
	return retval;
}

static int do_one_thumbnail(deark *c, lctx *d, i64 pos1, i64 imgidx, i64 *bytes_consumed)
{
	i64 payload_len;
	int retval = 0;
	i64 pos = pos1;
	UI filetype_code;
	i64 file_size;
	i64 x;
	i64 tn_w, tn_h;
	struct page_ctx *pg = NULL;
	i64 fn_field_size = 0;

	de_dbg(c, "image #%d at %"I64_FMT, (int)imgidx, pos1);
	de_dbg_indent(c, 1);

	pg = de_malloc(c, sizeof(struct page_ctx));

	pg->fi = de_finfo_create(c);

	if(!read_filename(c, d, pg, pos, &fn_field_size)) {
		goto done;
	}
	pos += fn_field_size;

	if(d->ver_major==2) {
		read_FILETIME(c, d, pg, pos);
		pos += 8;
	}

	if(d->ver_major==2) {
		// The original file type (not the format of the thumbnail)
		filetype_code = (UI)de_getu32le_p(&pos);
		de_dbg(c, "original file type: 0x%02x (%s)", filetype_code, get_type_name(filetype_code));
	}
	else if(d->ver_major==1 && d->ver_minor<3) {
		pos += 4; // TODO: FOURCC
	}

	tn_w = de_getu16le(pos);
	pos += 4;
	tn_h = de_getu16le(pos);
	pos += 4;
	de_dbg(c, "original dimensions: %d"DE_CHAR_TIMES"%d", (int)tn_w, (int)tn_h);

	pos += 4; // color depth

	if(d->ver_major==2) {
		pos += 4; // (uncompressed size?)
	}

	file_size = de_getu32le_p(&pos);
	de_dbg(c, "original file size: %"I64_FMT, file_size);

	if(d->ver_major==1) {
		read_unix_time(c, d, pg, pos);
		pos += 4;

		pos += 4; // TODO: image index
	}

	if(d->ver_major==2) {
		// first 4 bytes of 12-byte "thumbnail signature"
		x = de_getu32le_p(&pos);
		if(x==0) { // truncated entry
			de_dbg(c, "thumbnail not present");
			retval = 1;
			goto done;
		}

		pos += 8; // remaining 8 byte of signature

		payload_len = de_getu32le_p(&pos);
		de_dbg(c, "payload len: %"I64_FMT, payload_len);

		if(pos + payload_len > c->infile->len) {
			de_err(c, "Bad payload length (%"I64_FMT") or unsupported format", payload_len);
			goto done;
		}

		dbuf_create_file_from_slice(c->infile, pos, payload_len, pg->thumbnail_ext, pg->fi, 0);
		pos += payload_len;
	}
	else { // ver_major==1
		i64 thumbnail_size;
		if(!read_bitmap_v1(c, d, pg, pos, &thumbnail_size)) {
			goto done;
		}
		pos += thumbnail_size;
	}

	retval = 1;
done:
	*bytes_consumed = pos - pos1;
	de_finfo_destroy(c, pg->fi);
	ucstring_destroy(pg->fname);
	de_free(c, pg);
	de_dbg_indent(c, -1);
	return retval;
}

static void de_run_jbf(deark *c, de_module_params *mparams)
{
	lctx *d = NULL;
	i64 pos = 0;
	i64 bytes_consumed;
	i64 count = 0;

	d = de_malloc(c, sizeof(lctx));
	d->input_encoding = de_get_input_encoding(c, NULL, DE_ENCODING_WINDOWS1252);

	if(!do_read_header(c, d, pos)) goto done;
	pos += 1024;

	count = 0;
	while(1) {
		if(count>=d->image_count) break;
		if(pos>=c->infile->len) goto done;

		bytes_consumed = 0;
		if(!do_one_thumbnail(c, d, pos, count, &bytes_consumed)) {
			goto done;
		}
		if(bytes_consumed<1) goto done;
		pos += bytes_consumed;
		count++;
	}

done:
	de_free(c, d);
}

static int de_identify_jbf(deark *c)
{
	if(!dbuf_memcmp(c->infile, 0, "JASC BROWS FILE", 15))
		return 100;
	return 0;
}

void de_module_jbf(deark *c, struct deark_module_info *mi)
{
	mi->id = "jbf";
	mi->desc = "PaintShop Pro Browser Cache (pspbrwse.jbf)";
	mi->run_fn = de_run_jbf;
	mi->identify_fn = de_identify_jbf;
}
