/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * vsdump: test program to dump and parse content of vsd file
 *
 * Copyright (C) 2006-2007	Valek Filippov (frob@df.ru)
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 3 or later of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 *
 */
#define _GNU_SOURCE
#include <glib.h>
#include <glib/gstdio.h>
#include <gsf/gsf-utils.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <iconv.h>
#include <errno.h>
#include "vsdump.h"
#include "vsd_parse_chunks.h"
#include "vsd_parse_cmds.h"
#include "vsd_parse_blocks.h"

/*------------------------------------------------------------------------- */
/*
 * vsd_extract_chunk_hdr():
 * Takes stream, offset, version, pointer to VsdChunkHdr and pointer to trailer.
 * Fills VsdChunkHdr and trailer.
 *
 **/

static int
vsd_extract_chunk_hdr(GByteArray *stream, VsdChunkHdr *ch_hdr, guint *trailer, int *offset, int version)
{
	if(5 < version){
	ch_hdr->type = GSF_LE_GET_GUINT32(stream->data + *offset);
        if (0 == ch_hdr->type){
            *offset = *offset+4;
            return 0;
        }
	ch_hdr->ix = GSF_LE_GET_GUINT32(stream->data + 4 + *offset);
    ch_hdr->unkn1 = GSF_LE_GET_GUINT32(stream->data + 8 + *offset);
	ch_hdr->len = GSF_LE_GET_GUINT32(stream->data + 12 + *offset);
    ch_hdr->unkn2 = GSF_LE_GET_GUINT16(stream->data + 16 + *offset);
    ch_hdr->unkn3 = GSF_LE_GET_GUINT16(stream->data + 18 + *offset);
		if (0 != (int) ch_hdr->unkn1 || 0x71 == ch_hdr->type || 0x70 == ch_hdr->type) *trailer = 8;
		if (0x6b == ch_hdr->type || 0x6a == ch_hdr->type || 0x69 == ch_hdr->type || 0x66 == ch_hdr->type || 0x65 == ch_hdr->type || 0x2c == ch_hdr->type) *trailer = 8;
		if(11 == version){ /* separators were found only in Visio2k3 atm */
		 if(	
		 	(0 != (int) ch_hdr->unkn1) ||  /* trailer means that there is a separator too. */
	 		((2 == (int) ch_hdr->unkn2) &&  (0x55 == (int) ch_hdr->unkn3)) ||
	 		((2 == (int) ch_hdr->unkn2) &&  (0x54 == (int) ch_hdr->unkn3) && (0xaa == (int) ch_hdr->type)) ||
	 		((3 == (int) ch_hdr->unkn2) &&  (0x50 != (int) ch_hdr->unkn3)) ||
			(0x69 == ch_hdr->type || 0x6a == ch_hdr->type || 0x6b == ch_hdr->type || 0x71 == ch_hdr->type) ||
			(0xb4 == ch_hdr->type || 0xb6 == ch_hdr->type || 0xb9 == ch_hdr->type || 0xa9 == ch_hdr->type)
	 	    ){ 
			*trailer = *trailer + 4;
	 	 }
		}
		if( (11 == version) && (0x1f == ch_hdr->type || 0xc9 == ch_hdr->type ) ) *trailer = 0;
	}else{ /* versions 4 and 5 */
		ch_hdr->type = GSF_LE_GET_GUINT16(stream->data + *offset);
		ch_hdr->ix = GSF_LE_GET_GUINT16(stream->data + 2 + *offset);
        ch_hdr->unkn2 = GSF_LE_GET_GUINT8(stream->data + 4 + *offset);
        ch_hdr->unkn3 = GSF_LE_GET_GUINT8(stream->data + 5 + *offset);
        ch_hdr->unkn1 = GSF_LE_GET_GUINT16(stream->data + 6 + *offset);
		ch_hdr->len = GSF_LE_GET_GUINT32(stream->data + 8 + *offset);
	}
return 0;
}

/* ------------------------------------------------------- */
/* WILL KICK OUT *fdraw IN THE FUTURE! */
/* ------------------------------------------------------- */
int
vsd_parse_chunk(GArray *chunk_strm, GByteArray* stream, char *dirname, FILE *fdraw, GHashTable *vaetbls, int *offset, int *num, int version, char **ext, guint *numofparts, guint *counter)
{
	VsdChunkHdr		*ch_hdr, chnk_hdr;
	VsdChunk			*chn, chunk;
	VsdChunkItem	*chi, ch_item;
	VsdBlock			*bl=NULL;				
	GArray 			*vaet;
	VsdArgEntry		*vaep;
	guint				i=0, j,k, hex, nd, trailer=0, hdr_len=19, ch_type;
	FILE				*image;	
	char				*fullname, *tmpstr, *iconv_tr_str, *ch_name=NULL;
	char			units[] = "Unit=\"Unknown\"";
	iconv_t 		iconv_tr;

	ch_hdr = &chnk_hdr;
	chn = &chunk;
	chi = &ch_item;
	
	vsd_extract_chunk_hdr(stream, ch_hdr, &trailer, offset, version);

 	if(6 > version) hdr_len = 12;
	ch_type = ch_hdr->type;		
	
	if(NULL==(vaet = g_hash_table_lookup (vaetbls, GINT_TO_POINTER(ch_hdr->type)))){
		g_printf("Unknown chunk: %x.  Offset: %x. Len: %x Unkn1/2/3: %x %x %x. Trailer: %x\n",ch_hdr->type, *offset, ch_hdr->len, ch_hdr->unkn1, ch_hdr->unkn2, ch_hdr->unkn3, trailer);
		*offset= *offset + ch_hdr->len + trailer + hdr_len; 
		return 0;
	}
	
	vaep = &g_array_index(vaet,VsdArgEntry,0); /* 1st item is always name of chunk */
	g_printf("Chunk: %x. (%s) Offset: %x. Len: %x. Unkn1/2/3: %x %x %x. Trailer: %x\n",ch_hdr->type,*vaep->name, *offset, ch_hdr->len, ch_hdr->unkn1, ch_hdr->unkn2, ch_hdr->unkn3, trailer);

	for(i=0; i < vaet->len; i++){
		if(NULL == (vaep = &g_array_index(vaet,VsdArgEntry,i))) vaep->type = 18; /* dump if wasn't found */
		switch(vaep->type){
			case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
				fprintf(fdraw, "\t<%s>%s</%s>\n", *vaep->name, (stream->data[vaep->offset+*offset+hdr_len])&(1<<vaep->type) ? "TRUE" : "FALSE", *vaep->name);	
				break;
			case 8:
				fprintf(fdraw, "\t<%s>%d</%s>\n", *vaep->name, (stream->data[vaep->offset + *offset]), *vaep->name);	
				break;
			case 9:
				fprintf(fdraw, "\t<%s>%g</%s>\n", *vaep->name, GSF_LE_GET_DOUBLE(stream->data + vaep->offset+*offset), *vaep->name);	
				break;
			case 10:
				ch_name = *vaep->name;
				fprintf(fdraw, "<%s ID=\"%d\">\n", ch_name, ch_hdr->ix);
				break;
			case 11:
				if(11 == version) break; /* there is another 'start of block' command for 2k2/3 version */
				if((hdr_len + ch_hdr->len) > vaep->offset){
					j = *offset + vaep->offset;					
					fprintf(fdraw, "\t<Comment>There are some blocks in this chunk.</Comment>\n");
					while( j < (hdr_len + ch_hdr->len + *offset)){
						if(NULL != (bl = vsd_parse_blocks(stream, &j, version))){
							fprintf(fdraw, "\t<Block Variable %d>%s</Block Variable %d>\n", bl->idx, bl->formula, bl->idx);
						}else{
						g_print("Place of problem with blocks?\n");
/*						hex = 0;
						for(k = 0; k < (ch_hdr->len + hdr_len); k++){
							g_print("%02x ", stream->data[*offset+k]);
							hex++;
							if(16 == hex){
								g_print("\n");
								hex = 0;
							}						
						}
						g_print("\n");
*/
						}
					}
					free(bl);
				}				
				break;
			case 21:
				if(11 != version) break; /* there is another 'start of block' command for non-2k2/3 version */
				if((hdr_len + ch_hdr->len) > vaep->offset){
					j = *offset + vaep->offset;					
					fprintf(fdraw, "\t<Comment>There are some blocks in this chunk.</Comment>\n");
					while( j < (hdr_len + ch_hdr->len + *offset)){
						if(NULL != (bl = vsd_parse_blocks(stream, &j, version))){
							fprintf(fdraw, "\t<Block Variable %d>%s</Block Variable %d>\n", bl->idx, bl->formula, bl->idx);
						}
					}
					free(bl);
				}				
				break;
				 
			case 12: /* text */
				iconv_tr = iconv_open("UTF-8", "UCS-2");
				if (iconv_tr == (iconv_t) -1){
					  break;
				}	  
				size_t len1, len2, len3;
				len1 = ch_hdr->len + hdr_len - vaep->offset;
				len2= 6*len1;
				char *iconv_tr_str1, *iconv_tr_str2;
				iconv_tr_str = malloc(len2);
				iconv_tr_str2 = memset(iconv_tr_str, 0, len2);
				iconv_tr_str1 = (char *) &stream->data[vaep->offset+*offset];
				len3 = iconv(iconv_tr, &iconv_tr_str1,
					&len1, &iconv_tr_str, &len2);
				fprintf(fdraw, "%s\n",iconv_tr_str2);
				iconv_close(iconv_tr);
				free(iconv_tr_str2);
				break;

			case 16: /* use it for FontFace in Visio2000 only*/
				for(j= 6; j < (ch_hdr->len); j++){
					 if((31 < stream->data[j+hdr_len+*offset]) && (128 > stream->data[j+hdr_len+*offset]))
						fputc(stream->data[j+hdr_len+*offset],fdraw);
				}
				break;
			case 17: /* store ForeignData and OLEData here */
 				if(!strcmp("emf",*ext)){ /* need to distinguish between emf and wmf */
 				 j = GSF_LE_GET_GUINT32(stream->data + *offset + hdr_len + 40);
 				 if(0x464d4520 != j) *ext = "wmf";  /* EMF signature */ 
				}
				
				tmpstr = g_strdup_printf ("file%d.%s", *num, *ext);
        		fullname = g_build_path("/", dirname, tmpstr, NULL);
				image = fopen(fullname, "a"); /* open it to append */
 				 				
 				if(!strcmp("bmp",*ext)){ /* bmp file need special treatment */
					fprintf(image, "BM"); 											/* signature */
					hex = ch_hdr->len + 14; 
					GSF_LE_SET_GUINT32(&nd, hex);
					for(j=0;j<4;j++){
						fwrite(&((unsigned char *)&nd)[j], 1, 1, image); 	/* file length */
					}
					nd = 0;
					fwrite(&nd,1,4,image);											/* 'reserved' */
					
					j = GSF_LE_GET_GUINT32(stream->data + *offset + hdr_len + 20);
					if(j){
						hex = hex - j;
					}else{
						hex = 0x36;
					}
					GSF_LE_SET_GUINT32(&nd, hex);
					for(j=0;j<4;j++){
						fwrite(&((unsigned char *)&nd)[j], 1, 1, image); 	/* offset to data */
					}
				}

				for(j = 0; j < (ch_hdr->len); j++){
					fputc(stream->data[j+hdr_len+*offset],image);
				}
				
				(*counter)++;
				if(*counter == *numofparts){ /* i.e. we stored image file or all parts of OLE file */
					fprintf(fdraw, "\t<Comment>Embeded file. See file%d.%s </Comment>\n", *num, *ext);
					*counter = 0;
					*numofparts = 1; /* by default, and always(?) for images */
					(*num)++;
					fclose(image);
				}
				break;
			case 18:
				fprintf(fdraw, "\t<Dump>\n");
				hex=0;
				fprintf(fdraw,"\t!!!Dump!!! Chunk T/O/L: %x %x %x\n\t", ch_hdr->type, *offset, ch_hdr->len);
				
				for(j = 0; j < (ch_hdr->len); j++){

					if(stream->len < j + hdr_len + *offset) g_warning("OUT OF STREAM!!!");
				
					fprintf(fdraw,"%02x ",stream->data[j+hdr_len+*offset]);
					hex++;
					if(16 == hex){
					fprintf(fdraw, "\n\t");
					hex = 0;
					}
				}
				fprintf(fdraw, "\n\t</Dump>\n");
				break;
			case 25:
				fprintf(fdraw, "\t<%s>%d</%s>\n", *vaep->name, GSF_LE_GET_GUINT16(stream->data + vaep->offset+*offset), *vaep->name);	
				break;
			case 26:
				fprintf(fdraw, "\t<%s>%d</%s>\n", *vaep->name, GSF_LE_GET_GUINT32(stream->data + vaep->offset+*offset), *vaep->name);	
				break;	
			case 27: /*Experimental Tabs support */
				hex = stream->data[hdr_len+ *offset+4] + 1; /*num of structures */
				for(j = 0; j < hex; j++){
				 fprintf(fdraw, "\t<Tab ID %d><Pos>%g</Pos><Align>%d</Align></Tab ID %d>\n",
				 j, GSF_LE_GET_DOUBLE(stream->data + *offset + hdr_len+6 + 11*j), stream->data[*offset + hdr_len + 14 + 11*j], j);
				}					 
				break;
			case 28: /*Experimental image file extensions support */
				if(1 == stream->data[hdr_len+*offset+36]){
					switch (stream->data[hdr_len+*offset+49]){
						case 0:
							*ext = "bmp";
							break;
						case 1:
							*ext = "jpg";
							break;
						case 2:
							*ext = "gif";
							break;
						case 3:
							*ext = "tif";
							break;
						case 4:
							*ext = "png";
							break;
						default:
							*ext = "img";
					}
				}else{
					if(4 == stream->data[hdr_len+*offset+36]) *ext = "emf";
					if(2 == stream->data[hdr_len+*offset+36]) *ext = "ole"; /* default extension for OLE */
				}
				g_print("ext = %s. j = %d\n", *ext, stream->data[hdr_len+*offset+49]);
				break;
			case 29: /*Experimental OLE file extensions and merging from parts support */
					switch (stream->data[hdr_len+*offset+9]){
						case 0x1e:
							*ext = "xls";
							break;
						case 0x24:
							*ext = "ppt";
							break;
						case 0x34:
							*ext = "grf"; /* MS Graph */
							break;
						case 0x52:
							*ext = "doc";
							break;
						default:
							*ext = "ole";
					}
					*numofparts = stream->data[hdr_len+*offset+12];
				break;
		 	case 30:
			  	fprintf(fdraw, "\t<%s>%02x%02x%02x</%s>\n", *vaep->name,
					(stream->data[vaep->offset + *offset]),
					(stream->data[vaep->offset + *offset + 1]),
					(stream->data[vaep->offset + *offset + 2]), *vaep->name);
				break;
         case 31:
            switch(stream->data[vaep->offset + *offset]){
					case 0x46:
						strcpy(units, "Unit=\"MM\"");
						break;
					case 0x30:
						strcpy(units, "Unit=\"DT\"");
						break;
					case 0x32:
						strcpy(units, "Unit=\"PT\"");
						break;
					case 0x49:
 						strcpy(units, "Unit=\"IN_F\"");
 						break;
					case 0x50:
						strcpy(units, "Unit=\"DA\"");
						break;
 					case 0x51: // looks like RAD but DEG in vdx
 						strcpy(units, "Unit=\"DEG\"");
 						break;
					default:
						strcpy(units, "");
				}
				fprintf(fdraw, "\t<%s %s>%g</%s>\n", *vaep->name, units,
					GSF_LE_GET_DOUBLE(stream->data + vaep->offset+*offset + 1), *vaep->name);
            break;
			default:
				g_warning("There is a PROBLEM with the command file! Unknown 'Type' %d!", vaep->type);
				break;			
		}
	}	
	if(NULL != ch_name) fprintf(fdraw, "</%s>\n", ch_name);
	*offset= *offset + ch_hdr->len + trailer + hdr_len;	
			
return 0;	
}
