/*
 *          GPAC - Multimedia Framework C SDK
 *
 *          Authors: Cyril Concolato, Jean Le Feuvre
 *          Copyright (c) Telecom ParisTech 2000-2022
 *                  All rights reserved
 *
 *  This file is part of GPAC / Scene Management sub-project
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  GPAC 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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */
#include <gpac/utf.h>
#include <gpac/xml.h>
#include <gpac/internal/swf_dev.h>
#include <gpac/internal/scenegraph_dev.h>
#include <gpac/internal/isomedia_dev.h>
#include <gpac/internal/media_dev.h>
#include <gpac/constants.h>

#ifndef GPAC_DISABLE_VVG

#ifndef GPAC_DISABLE_SWF_IMPORT

#define SWF_TEXT_SCALE              (1/1024.0f)

typedef struct _swf_svg_sample
{
	u64 start;
	u64 end;
	char *data;
} GF_SWF_SVG_Sample;

static void swf_svg_print(SWFReader *read, const char *format, ...) {
	char line[2000];
	u32 line_length;
	u32 new_size;
	va_list args;

	/* print the line */
	va_start(args, format);
	vsnprintf(line, 2000, format, args);
	va_end(args);
	/* add the line to the buffer */
	line_length = (u32)strlen(line);
	new_size = read->svg_data_size+line_length;
	read->svg_data = (char *)gf_realloc(read->svg_data, new_size+1);
	if (read->print_frame_header) {
		/* write at the beginning of the buffer */
		memmove(read->svg_data+read->frame_header_offset+line_length, read->svg_data+read->frame_header_offset, (read->svg_data_size-read->frame_header_offset)+1);
		memcpy(read->svg_data+read->frame_header_offset, line, line_length);
		read->frame_header_offset += line_length;
	} else {
		strcpy(read->svg_data+read->svg_data_size, line);
	}
	read->svg_data_size = new_size;
}

static void swf_svg_print_color(SWFReader *read, u32 ARGB)
{
	SFColor val;
	val.red = INT2FIX((ARGB>>16)&0xFF) / 255*100;
	val.green = INT2FIX((ARGB>>8)&0xFF) / 255*100;
	val.blue = INT2FIX((ARGB)&0xFF) / 255*100;
	swf_svg_print(read, "rgb(%g%%,%g%%,%g%%)", FIX2FLT(val.red), FIX2FLT(val.green), FIX2FLT(val.blue));
}

static void swf_svg_print_alpha(SWFReader *read, u32 ARGB)
{
	Fixed alpha;
	alpha = INT2FIX((ARGB>>24)&0xFF)/255;
	swf_svg_print(read, "%g", FIX2FLT(alpha));
}

static void swg_svg_print_shape_record_to_fill_stroke(SWFReader *read, SWFShapeRec *srec, Bool is_fill)
{
	/*get regular appearance reuse*/
	if (is_fill) {
		switch (srec->type) {
		/*solid/alpha fill*/
		case 0x00:
			swf_svg_print(read, "fill=\"");
			swf_svg_print_color(read, srec->solid_col);
			swf_svg_print(read, "\" ");
			swf_svg_print(read, "fill-opacity=\"");
			swf_svg_print_alpha(read, srec->solid_col);
			swf_svg_print(read, "\" ");
			break;
		case 0x10:
		case 0x12:
		//if (read->flags & GF_SM_SWF_NO_GRADIENT) {
		//	u32 col = srec->grad_col[srec->nbGrad/2];
		//	col |= 0xFF000000;
		//	n->appearance = s2b_get_appearance(read, (GF_Node *) n, col, 0, 0);
		//} else {
		//	n->appearance = s2b_get_gradient(read, (GF_Node *) n, shape, srec);
		//}
		//break;
		case 0x40:
		case 0x41:
		case 0x42:
		case 0x43:
		//n->appearance = s2b_get_bitmap(read, (GF_Node *) n, shape, srec);
		//break;
		default:
			swf_report(read, GF_NOT_SUPPORTED, "fill_style %x not supported", srec->type);
			break;
		}
	} else {
		swf_svg_print(read, "fill=\"none\" ");
		swf_svg_print(read, "stroke=\"");
		swf_svg_print_color(read, srec->solid_col);
		swf_svg_print(read, "\" ");
		swf_svg_print(read, "stroke-opacity=\"");
		swf_svg_print_alpha(read, srec->solid_col);
		swf_svg_print(read, "\" ");
		swf_svg_print(read, "stroke-width=\"%g\" ", FIX2FLT(srec->width));
	}
}

static void swf_svg_print_shape_record_to_path_d(SWFReader *read, SWFShapeRec *srec)
{
	u32     pt_idx;
	u32     i;

	pt_idx = 0;
	for (i=0; i<srec->path->nbType; i++) {
		switch (srec->path->types[i]) {
		/*moveTo*/
		case 0:
			swf_svg_print(read, "M%g,%g", FIX2FLT(srec->path->pts[pt_idx].x), FIX2FLT(srec->path->pts[pt_idx].y));
			pt_idx++;
			break;
		/*lineTo*/
		case 1:
			swf_svg_print(read, "L%g,%g", FIX2FLT(srec->path->pts[pt_idx].x), FIX2FLT(srec->path->pts[pt_idx].y));
			pt_idx++;
			break;
		/*curveTo*/
		case 2:
			swf_svg_print(read, "Q%g,%g", FIX2FLT(srec->path->pts[pt_idx].x), FIX2FLT(srec->path->pts[pt_idx].y));
			pt_idx++;
			swf_svg_print(read, ",%g,%g", FIX2FLT(srec->path->pts[pt_idx].x), FIX2FLT(srec->path->pts[pt_idx].y));
			pt_idx++;
			break;
		}
	}
}

static void swf_svg_print_matrix(SWFReader *read, GF_Matrix2D *mat)
{
	if (!gf_mx2d_is_identity(*mat))
	{
		GF_Point2D  scale;
		GF_Point2D  translate;
		Fixed       rotate;
		if( gf_mx2d_decompose(mat, &scale, &rotate, &translate))
		{
			swf_svg_print(read, "transform=\"");
			if (translate.x != 0 || translate.y != 0)
			{
				swf_svg_print(read, "translate(%g, %g) ", FIX2FLT(translate.x), FIX2FLT(translate.y));
			}
			if (rotate != 0)
			{
				swf_svg_print(read, "rotate(%g) ", FIX2FLT(rotate));
			}
			if (scale.x != FIX_ONE || scale.y != FIX_ONE)
			{
				swf_svg_print(read, "scale(%g, %g) ", FIX2FLT(scale.x), FIX2FLT(scale.y));
			}
			swf_svg_print(read, "\" ");
		}
		else
		{
			swf_svg_print(read, "transform=\"matrix(%g,%g,%g,%g,%g,%g)\" ", FIX2FLT(mat->m[0]), FIX2FLT(mat->m[3]), FIX2FLT(mat->m[1]), FIX2FLT(mat->m[4]), FIX2FLT(mat->m[2]), FIX2FLT(mat->m[5]) );
		}
	}
}

/*translates Flash to SVG shapes*/
static GF_Err swf_svg_define_shape(SWFReader *read, SWFShape *shape, SWFFont *parent_font, Bool last_sub_shape)
{
	u32 i;
	SWFShapeRec *srec;

	if (parent_font && (read->flags & GF_SM_SWF_NO_FONT))
	{
		return GF_OK;
	}

	if (!read->svg_shape_started)
	{
		swf_svg_print(read, "<defs>\n");
		if (!parent_font)
		{
			swf_svg_print(read, "<g id=\"S%d\" >\n", shape->ID);
		}
		else
		{
			char    szGlyphId[256];
			sprintf(szGlyphId, "Font%d_Glyph%d", parent_font->fontID, gf_list_count(parent_font->glyphs));
			swf_svg_print(read, "<g id=\"%s\" >\n", szGlyphId);
			gf_list_add(parent_font->glyphs, szGlyphId);
		}
	}
	read->empty_frame = GF_FALSE;
	read->svg_shape_started = GF_TRUE;

	i=0;
	while ((srec = (SWFShapeRec*)gf_list_enum(shape->fill_left, &i))) {
		swf_svg_print(read, "<path d=\"");
		swf_svg_print_shape_record_to_path_d(read, srec);
		swf_svg_print(read, "\" ");
		swg_svg_print_shape_record_to_fill_stroke(read, srec, 1);
		swf_svg_print(read, "/>\n");
	}
	i=0;
	while ((srec = (SWFShapeRec*)gf_list_enum(shape->lines, &i))) {
		swf_svg_print(read, "<path d=\"");
		swf_svg_print_shape_record_to_path_d(read, srec);
		swf_svg_print(read, "\" ");
		swg_svg_print_shape_record_to_fill_stroke(read, srec, 0);
		swf_svg_print(read, "/>\n");
	}

	if (last_sub_shape)
	{
		read->svg_shape_started = GF_FALSE;
		swf_svg_print(read, "</g>\n");
		swf_svg_print(read, "</defs>\n");
	}
	return GF_OK;
}

static GF_Err swf_svg_define_text(SWFReader *read, SWFText *text)
{
	Bool            use_text;
	u32             i;
	u32             j;
	SWFGlyphRec     *gr;
	SWFFont         *ft;

	use_text = (read->flags & GF_SM_SWF_NO_FONT) ? 1 : 0;

	swf_svg_print(read, "<defs>\n");
	swf_svg_print(read, "<g id=\"S%d\" ", text->ID);
	swf_svg_print_matrix(read, &text->mat);
	swf_svg_print(read, ">\n");

	i=0;
	while ((gr = (SWFGlyphRec*)gf_list_enum(text->text, &i)))
	{
		ft = NULL;
		if (use_text) {
			ft = swf_find_font(read, gr->fontID);
			if (!ft->glyph_codes) {
				use_text = 0;
				swf_report(read, GF_BAD_PARAM, "Font glyphs are not defined, cannot reference extern font - Forcing glyph embedding");
			}
		}
		if (use_text) {
			/*restore back the font height in pixels (it's currently in SWF glyph design units)*/
			swf_svg_print(read, "<text ");
			swf_svg_print(read, "x=\"%g \" ", FIX2FLT(gr->orig_x));
			swf_svg_print(read, "y=\"%g \" ", FIX2FLT(gr->orig_y));
			swf_svg_print(read, "font-size=\"%d\" ", (u32)(gr->fontSize * SWF_TWIP_SCALE));
			if (ft->fontName)
			{
				swf_svg_print(read, "font-family=\"%s\" ", ft->fontName);
			}
			if (ft->is_italic)
			{
				swf_svg_print(read, "font-style=\"italic\" ");
			}
			if (ft->is_bold)
			{
				swf_svg_print(read, "font-weight=\"bold\" ");
			}
			swf_svg_print(read, ">");
			/*convert to UTF-8*/
			{
				u32 _len;
				u16     *str_w;
				u16     *widestr;
				char    *str;

				str_w = (u16*)gf_malloc(sizeof(u16) * (gr->nbGlyphs+1));
				for (j=0; j<gr->nbGlyphs; j++)
				{
					str_w[j] = ft->glyph_codes[gr->indexes[j]];
				}
				str_w[j] = 0;
				str = (char*)gf_malloc(sizeof(char) * (gr->nbGlyphs+2));
				widestr = str_w;
				_len = gf_utf8_wcstombs(str, sizeof(u8) * (gr->nbGlyphs+1), (const unsigned short **) &widestr);
				if (_len != GF_UTF8_FAIL) {
					str[_len] = 0;
					swf_svg_print(read, "%s", str);
				}
			}
			swf_svg_print(read, "</text>\n");
		}
		else
		{
			/*convert glyphs*/
			Fixed       dx;
			swf_svg_print(read, "<g tranform=\"scale(1,-1) ");
			swf_svg_print(read, "translate(%g, %g)\" >\n", FIX2FLT(gr->orig_x), FIX2FLT(gr->orig_y));

			dx = 0;
			for (j=0; j<gr->nbGlyphs; j++)
			{
				swf_svg_print(read, "<use xlink:href=\"#Font%d_Glyph%d\" transform=\"translate(%g)\" />\n", gr->fontID, gr->indexes[j], FIX2FLT(gf_divfix(dx, FLT2FIX(gr->fontSize * SWF_TEXT_SCALE))));
				dx += gr->dx[j];
			}
			swf_svg_print(read, "</g>\n");
		}
	}
	read->empty_frame = GF_FALSE;
	swf_svg_print(read, "</g>\n");
	swf_svg_print(read, "</defs>\n");
	return GF_OK;
}

static GF_Err swf_svg_define_edit_text(SWFReader *read, SWFEditText *text)
{
	return GF_OK;
}

#if 0
/*called upon end of sprite or clip*/
static void swf_svg_end_of_clip(SWFReader *read)
{
}
#endif

static Bool swf_svg_allocate_depth(SWFReader *read, u32 depth)
{
	return GF_FALSE;
}

static GF_Err swf_svg_define_sprite(SWFReader *read, u32 nb_frames)
{
	return GF_OK;
}

static GF_Err swf_svg_setup_sound(SWFReader *read, SWFSound *snd, Bool soundstream_first_block)
{
	return GF_OK;
}

static GF_Err swf_svg_setup_image(SWFReader *read, u32 ID, char *fileName)
{
	swf_svg_print(read, "<defs>\n");
	swf_svg_print(read, "<image id=\"S%d\" xlink:href=\"\"/>", ID, fileName);
	swf_svg_print(read, "</defs>\n");
	return GF_OK;
}

static GF_Err swf_svg_set_backcol(SWFReader *read, u32 xrgb)
{

	//rgb.red = INT2FIX((xrgb>>16) & 0xFF) / 255;
	//rgb.green = INT2FIX((xrgb>>8) & 0xFF) / 255;
	//rgb.blue = INT2FIX((xrgb) & 0xFF) / 255;
	return GF_OK;
}

static GF_Err swf_svg_start_sound(SWFReader *read, SWFSound *snd, Bool stop)
{
	return GF_OK;
}

static GF_Err swf_svg_place_obj(SWFReader *read, u32 depth, u32 ID, u32 prev_id, u32 type, GF_Matrix2D *mat, GF_ColorMatrix *cmat, GF_Matrix2D *prev_mat, GF_ColorMatrix *prev_cmat)
{
	read->empty_frame = GF_FALSE;
	return GF_OK;
}

static GF_Err swf_svg_remove_obj(SWFReader *read, u32 depth, u32 ID)
{
	read->empty_frame = GF_FALSE;
	return GF_OK;
}

static GF_Err swf_svg_show_frame(SWFReader *read)
{
	u32     i;
	u32     len;
	GF_List *sdl = gf_list_new(); // sorted display list

	/* sorting the display list because SVG/CSS z-index is not well supported */
	while (gf_list_count(read->display_list))
	{
		Bool        inserted = GF_FALSE;
		DispShape   *s;

		s = (DispShape *)gf_list_get(read->display_list, 0);
		gf_list_rem(read->display_list, 0);

		for (i = 0; i < gf_list_count(sdl); i++)
		{
			DispShape *s2 = (DispShape *)gf_list_get(sdl, i);
			if (s->depth < s2->depth)
			{
				gf_list_insert(sdl, s, i);
				inserted = GF_TRUE;
				break;
			}
		}
		if (!inserted)
		{
			gf_list_add(sdl, s);
		}
	}
	gf_list_del(read->display_list);
	read->display_list = sdl;

	/* dumping the display list */
	len = gf_list_count(read->display_list);
	for (i=0; i<len; i++)
	{
		DispShape   *s;
		s = (DispShape *)gf_list_get(read->display_list, i);
		swf_svg_print(read, "<use xlink:href=\"#S%d\" ", s->char_id);
		//swf_svg_print(read, "z-index=\"%d\" ", s->depth);
		swf_svg_print_matrix(read, &s->mat);
		swf_svg_print(read, "/>\n");
		read->empty_frame = GF_FALSE;
	}
	if (!read->empty_frame) {
		read->print_frame_header = GF_TRUE;
		read->frame_header_offset = 0;
		swf_svg_print(read, "<g display=\"none\">\n");
		swf_svg_print(read, "<animate id=\"frame%d_anim\" attributeName=\"display\" to=\"inline\" ", read->current_frame);
		swf_svg_print(read, "begin=\"%g\" ", 1.0*(read->current_frame)/read->frame_rate);
		if (read->current_frame+1 < read->frame_count) {
			swf_svg_print(read, "end=\"frame%d_anim.begin\" fill=\"remove\" ", (read->current_frame+1));
		} else {
			swf_svg_print(read, "fill=\"freeze\" ");
		}
		swf_svg_print(read, "/>\n");
		read->print_frame_header = GF_FALSE;

		swf_svg_print(read, "</g>\n");
	}
	read->add_sample(read->user, read->svg_data, read->svg_data_size, read->current_frame*1000/read->frame_rate, (read->current_frame == 0));
	gf_free(read->svg_data);
	read->svg_data = NULL;
	read->svg_data_size = 0;

	read->empty_frame = GF_TRUE;
	return GF_OK;
}

static void swf_svg_finalize(SWFReader *read, Bool is_destroy)
{
	if (!is_destroy) {
		swf_svg_print(read, "</svg>\n");
		read->add_header(read->user, read->svg_data, read->svg_data_size, GF_FALSE);
	}
	gf_free(read->svg_data);
	read->svg_data = NULL;
	read->svg_data_size = 0;
}

static GF_Err swf_svg_define_button(SWFReader *read, SWF_Button *btn)
{
	return GF_OK;
}

static Bool swf_svg_action(SWFReader *read, SWFAction *act)
{
	return GF_TRUE;
}

GF_Err swf_to_svg_init(SWFReader *read, u32 swf_flags, Float swf_flatten_angle)
{
	if (!read->user) return GF_BAD_PARAM;

	/*init callbacks*/
	read->show_frame = swf_svg_show_frame;
	read->allocate_depth = swf_svg_allocate_depth;
	read->place_obj = swf_svg_place_obj;
	read->remove_obj = swf_svg_remove_obj;
	read->define_shape = swf_svg_define_shape;
	read->define_sprite = swf_svg_define_sprite;
	read->set_backcol = swf_svg_set_backcol;
	read->define_button = swf_svg_define_button;
	read->define_text = swf_svg_define_text;
	read->define_edit_text = swf_svg_define_edit_text;
	read->setup_sound = swf_svg_setup_sound;
	read->start_sound = swf_svg_start_sound;
	read->setup_image = swf_svg_setup_image;
	read->action = swf_svg_action;
	read->finalize = swf_svg_finalize;

	read->flags = swf_flags;
	read->flat_limit = FLT2FIX(swf_flatten_angle);

	read->print_stream_header = GF_TRUE;
	swf_svg_print(read, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
	swf_svg_print(read, "<svg xmlns=\"http://www.w3.org/2000/svg\" ");
	swf_svg_print(read, "xmlns:xlink=\"http://www.w3.org/1999/xlink\" ");
	swf_svg_print(read, "width=\"100%%\" ");
	swf_svg_print(read, "height=\"100%%\" ");
	swf_svg_print(read, "viewBox=\"0 0 %d %d\" ", FIX2INT(read->width), FIX2INT(read->height));
	swf_svg_print(read, "viewport-fill=\"rgb(255,255,255)\" ");
	swf_svg_print(read, ">\n");
	read->print_stream_header = GF_FALSE;

	/* update sample description */
	read->add_header(read->user, read->svg_data, read->svg_data_size, GF_TRUE);
	gf_free(read->svg_data);
	read->svg_data = NULL;
	read->svg_data_size = 0;

	return GF_OK;
}

GF_Err swf_svg_write_text_sample(void *user, const u8 *data, u32 length, u64 timestamp, Bool isRap)
{
	FILE *svgFile = (FILE *)user;
	u32  lengthWritten;

	lengthWritten = (u32) gf_fwrite(data, length, svgFile);
	if (length != lengthWritten) {
		return GF_IO_ERR;
	} else {
		return GF_OK;
	}
}

GF_Err swf_svg_write_text_header(void *user, const u8 *data, u32 length, Bool isHeader)
{
	FILE *svgFile = (FILE *)user;
	u32  lengthWritten;

	lengthWritten = (u32) gf_fwrite(data, length, svgFile);
	if (length != lengthWritten) {
		return GF_IO_ERR;
	} else {
		return GF_OK;
	}
}
#endif /*GPAC_DISABLE_SWF_IMPORT*/

#endif /*GPAC_DISABLE_SVG*/
