void print_dependencies(char *name)
{
	struct pkglist *p = NULL;
	p = get_dependencies(name);
	while (p != NULL) {
		if (strcmp(p->repo, "not found") == 0) {
			printf("%s [not found]\n", p->name);
			p = p->next;
			continue;
		}

		printf("%s [", p->name);

		if (pkglist_exists(p->name, ilenia_pkgs) != 0) {
			printf(" ]\n");
			p = p->next;
			continue;
		}
		printf("installed]");

		struct pkglist *paus = NULL;
		paus = pkglist_find(p->name, ilenia_pkgs);

		if (strcmp(paus->version, "alias") == 0)
			printf(" (%s)\n", paus->repo);
		else
			printf("\n");
		p = p->next;
	}
}
/** OTFUR algorithm **/
otfur_result*
otfur(antichain *safety_game_PO, antichain *safety_game_PI, GNode* cfinfo, alphabet_info *alphabet, char starting_player, int dimension, int* max_credit) {
	clock_t start_time = clock();

	// Structures initialization
	GHashTable *succ_to_visit = g_hash_table_new_full(hash_key, compare_keys, free, NULL);
	GHashTable *passed = g_hash_table_new_full(hash_key, compare_keys, (GDestroyNotify)free_hash_table_key, free);
	GHashTable *depend = g_hash_table_new_full(hash_key, compare_keys, free, NULL);
	//GList *trash = NULL;

	GList *waiting = NULL;
	antichain *losing_PO = new_antichain();
	antichain *losing_PI = new_antichain();

	// Build initial tuple
	tuple *s_ini = build_initial_tuple(cfinfo, dimension, max_credit);

	// if s_ini belongs to the system and all its successors are unsafe, s_ini is losing
	if((starting_player == P_O && has_successor_in_safety_game(s_ini, alphabet, safety_game_PI) == FALSE)/* ||
			(starting_player == P_I && has_successor_not_in_safety_game(s_ini, alphabet, safety_game_PO) == TRUE)*/) { // To avoid k differences between forward and backward algorithms
		add_to_losing(s_ini, losing_PO, losing_PI);
	} // else, explore its successors
	else {
		waiting = add_to_waiting(s_ini, alphabet, safety_game_PO, safety_game_PI, losing_PO, losing_PI, waiting, succ_to_visit, passed);
		add_to_passed(s_ini, passed);
	}

	safety_game_edge *cur_edge;
	char cur_losing;
	int nb_cf_passed = 1;
	int nb_iter = 0;
	antichain* cur_safety_game;
	while(waiting != NULL && is_losing(s_ini, losing_PO, losing_PI) == FALSE) {
		nb_iter++;
		GList *last_link = g_list_last(waiting);
		cur_edge = (safety_game_edge*)last_link->data;
		waiting = remove_last(waiting);
		if(is_losing(cur_edge->from, losing_PO, losing_PI) == FALSE) { // If s is not losing
			if(is_passed(cur_edge->to, passed) == FALSE) { // If s' is not passed
				add_to_passed(cur_edge->to, passed);
				nb_cf_passed++;
				if(is_losing(cur_edge->to, losing_PO, losing_PI) == TRUE) { // If s' is losing -> add e for reevaluation
					//waiting = g_list_append(waiting, clone_safety_game_edge(cur_edge));
					waiting = g_list_append(waiting, cur_edge);
				}
				// Basic case: if s' belongs to P_O and has no successor safe non losing, add it to losing
				// Put the same test for P_I to avoid k differences between forward and backward algorithms
				else if(cur_edge->to->cf->player == P_O && has_successor_non_losing_in_safety_game(cur_edge->to, alphabet, losing_PO, losing_PI, safety_game_PI) == FALSE) {
					//waiting = g_list_append(waiting, clone_safety_game_edge(cur_edge));
					waiting = g_list_append(waiting, cur_edge);
					add_to_losing(cur_edge->to, losing_PO, losing_PI);
				}
				else { // else, explore its successors
					add_to_depend(cur_edge->to, cur_edge, depend);
					waiting = add_to_waiting(cur_edge->to, alphabet, safety_game_PO, safety_game_PI, losing_PO, losing_PI, waiting, succ_to_visit, passed);
				}
			}
			else { // s' is passed
				if(cur_edge->from->cf->player == P_O) { // s belongs to P_O
					if(is_losing(cur_edge->to, losing_PO, losing_PI) == FALSE) { // if s' is not losing, add e to the dependencies of s'
						add_to_depend(cur_edge->to, cur_edge, depend);
					}
					else { // if s' is losing, and if there is no more successor of s non losing to visit, s is losing -> add its dependencies to waiting
						if(has_one_succ_to_visit_non_losing(cur_edge->from, succ_to_visit, losing_PO, losing_PI) == FALSE) {
							add_to_losing(cur_edge->from, losing_PO, losing_PI);
							waiting = g_list_concat(waiting, get_dependencies(cur_edge->from, depend));
						}
						else { // if there is still at least a successor non passed non losing, add it to waiting
							waiting = g_list_append(waiting, get_first_successor_passed_non_losing(cur_edge->from, succ_to_visit, passed, losing_PO, losing_PI));
						}
					}
				}
				else { // s belongs to P_I
					// reevaluation
					if(is_losing(cur_edge->to, losing_PO, losing_PI) == TRUE) {
						cur_losing = TRUE;
					}
					else {
						cur_losing = reevaluation(cur_edge->from, alphabet, losing_PO, losing_PI);
					}

					// if s is losing, add its dependencies to waiting
					if(cur_losing == TRUE) {
						add_to_losing(cur_edge->from, losing_PO, losing_PI);
						waiting = g_list_concat(waiting, get_dependencies(cur_edge->from, depend));
					}

					// if s' is not losing, add e to its dependencies list
					if(is_losing(cur_edge->to, losing_PO, losing_PI) == FALSE) {
						add_to_depend(cur_edge->to, cur_edge, depend);
					}
				}
				//trash = g_list_append(trash, cur_edge->to);
			}
		}
		else { // if s is losing, add its dependencies to waiting
			//free_tuple_full(cur_edge->to);
			waiting = g_list_concat(waiting, get_dependencies(cur_edge->from, depend));
		}
	}
	float otfur_time = (clock() - start_time) * 1e-6;

	// Extract the solution from passed and losing
	start_time = clock();
	safety_game* sg = compute_winning_positions(losing_PO, losing_PI, passed, build_initial_tuple(cfinfo, dimension, max_credit), alphabet, nb_cf_passed);
	float winning_positions_computation_time = (clock() - start_time) * 1e-6;

	// Free memory
	GList *curlink = waiting;
	//while(curlink != NULL) {
	//	free_safety_game_edge(curlink->data);
	//	curlink = curlink->next;
	//}
	g_list_free(waiting);
	/*curlink = trash;
	while(curlink != NULL) {
		free_tuple_full(curlink->data);
		curlink = curlink->next;
	}
	g_list_free(trash);*/
	//g_hash_table_destroy(passed); // this one is not correct !
	g_hash_table_foreach(depend, free_depend, NULL);
	g_hash_table_destroy(depend);
	g_hash_table_foreach(succ_to_visit, free_succ_to_visit, NULL);
	g_hash_table_destroy(succ_to_visit);

	// Build result
	otfur_result *res = (otfur_result*)malloc(sizeof(otfur_result));
	res->winning_positions = sg;
	res->otfur_time = otfur_time;
	res->winning_positions_computation_time = winning_positions_computation_time;
	res->nb_cf_passed = nb_cf_passed;
	res->nb_iter = nb_iter;

	return res;
}
Error EditorExportPlatform::export_project_files(EditorExportSaveFunction p_func, void* p_udata,bool p_make_bundles) {

/* ALL FILES AND DEPENDENCIES */

	Vector<StringName> files=get_dependencies(p_make_bundles);

/* GROUP ATLAS */


	List<StringName> groups;

	EditorImportExport::get_singleton()->image_export_get_groups(&groups);

	Map<StringName,StringName> remap_files;
	Set<StringName> saved;

	int counter=0;

	for(List<StringName>::Element *E=groups.front();E;E=E->next()) {

		if (!EditorImportExport::get_singleton()->image_export_group_get_make_atlas(E->get()))
			continue; //uninterested, only process for atlas!

		List<StringName> atlas_images;
		EditorImportExport::get_singleton()->image_export_get_images_in_group(E->get(),&atlas_images);
		atlas_images.sort_custom<StringName::AlphCompare>();

		for (List<StringName>::Element *F=atlas_images.front();F;) {

			List<StringName>::Element *N=F->next();

			if (!FileAccess::exists(F->get())) {
				atlas_images.erase(F);
			}

			F=N;

		}

		if (atlas_images.size()<=1)
			continue;

		int group_format=0;
		float group_lossy_quality=EditorImportExport::get_singleton()->image_export_group_get_lossy_quality(E->get());
		int group_shrink=EditorImportExport::get_singleton()->image_export_group_get_shrink(E->get());
		group_shrink*=EditorImportExport::get_singleton()->get_export_image_shrink();

		switch(EditorImportExport::get_singleton()->image_export_group_get_image_action(E->get())) {
			case EditorImportExport::IMAGE_ACTION_NONE: {

				switch(EditorImportExport::get_singleton()->get_export_image_action()) {
					case EditorImportExport::IMAGE_ACTION_NONE: {

						group_format=EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_DISK_LOSSLESS; //?

					} break; //use default
					case EditorImportExport::IMAGE_ACTION_COMPRESS_DISK: {
						group_format=EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_DISK_LOSSY;
					} break; //use default
					case EditorImportExport::IMAGE_ACTION_COMPRESS_RAM: {
						group_format=EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_RAM;
					} break; //use default
				}

				group_lossy_quality=EditorImportExport::get_singleton()->get_export_image_quality();

			} break; //use default
			case EditorImportExport::IMAGE_ACTION_COMPRESS_DISK: {
				group_format=EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_DISK_LOSSY;
			} break; //use default
			case EditorImportExport::IMAGE_ACTION_COMPRESS_RAM: {
				group_format=EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_RAM;
			} break; //use default
		}

		String image_list_md5;

		{
			MD5_CTX ctx;
			MD5Init(&ctx);
			for (List<StringName>::Element *F=atlas_images.front();F;F=F->next()) {

				String p = F->get();
				MD5Update(&ctx,(unsigned char*)p.utf8().get_data(),p.utf8().length());

			}

			MD5Final(&ctx);
			image_list_md5=String::md5(ctx.digest);
		}
		//ok see if cached
		String md5;
		bool atlas_valid=true;
		String atlas_name;

		{
			MD5_CTX ctx;
			MD5Init(&ctx);
			String path = Globals::get_singleton()->get_resource_path()+"::"+String(E->get())+"::"+get_name();
			MD5Update(&ctx,(unsigned char*)path.utf8().get_data(),path.utf8().length());
			MD5Final(&ctx);
			md5 = String::md5(ctx.digest);
		}

		FileAccess *f=NULL;

		if (!FileAccess::exists(EditorSettings::get_singleton()->get_settings_path()+"/tmp/atlas-"+md5)) {
			print_line("NO MD5 INVALID");
			atlas_valid=false;
		}

		if (atlas_valid)
			f=FileAccess::open(EditorSettings::get_singleton()->get_settings_path()+"/tmp/atlas-"+md5,FileAccess::READ);

		if (atlas_valid) {
			//compare options
			Dictionary options;
			options.parse_json(f->get_line());
			if (!options.has("lossy_quality") || float(options["lossy_quality"])!=group_lossy_quality)
				atlas_valid=false;
			else if (!options.has("shrink") || int(options["shrink"])!=group_shrink)
				atlas_valid=false;
			else if (!options.has("image_format") || int(options["image_format"])!=group_format)
				atlas_valid=false;

			if (!atlas_valid)
				print_line("JSON INVALID");

		}


		if (atlas_valid) {
			//check md5 of list of image /names/
			if (f->get_line().strip_edges()!=image_list_md5) {
				atlas_valid=false;
				print_line("IMAGE MD5 INVALID!");
			}

		}

		Vector<Rect2> rects;
		bool resave_deps=false;

		if (atlas_valid) {

			//check if images were not modified
			for (List<StringName>::Element *F=atlas_images.front();F;F=F->next()) {

				Vector<String> slices = f->get_line().strip_edges().split("::");

				if (slices.size()!=10) {
					atlas_valid=false;
					print_line("CANT SLICE IN 10");
					break;
				}
				uint64_t mod_time = slices[0].to_int64();
				uint64_t file_mod_time = FileAccess::get_modified_time(F->get());
				if (mod_time!=file_mod_time) {

					String image_md5 = slices[1];
					String file_md5 = FileAccess::get_md5(F->get());

					if (image_md5!=file_md5) {
						atlas_valid=false;
						print_line("IMAGE INVALID "+slices[0]);
						break;
					} else {
						resave_deps=true;
					}
				}

				if (atlas_valid) {
					//push back region and margin
					rects.push_back(Rect2(slices[2].to_float(),slices[3].to_float(),slices[4].to_float(),slices[5].to_float()));
					rects.push_back(Rect2(slices[6].to_float(),slices[7].to_float(),slices[8].to_float(),slices[9].to_float()));
				}
			}

		}

		if (f) {
			memdelete(f);
			f=NULL;
		}

		print_line("ATLAS VALID? "+itos(atlas_valid)+" RESAVE DEPS? "+itos(resave_deps));
		if (!atlas_valid) {
			rects.clear();
			//oh well, atlas is not valid. need to make new one....

			String dst_file = EditorSettings::get_singleton()->get_settings_path()+"/tmp/atlas-"+md5+".tex";
			Ref<ResourceImportMetadata> imd = memnew( ResourceImportMetadata );
			//imd->set_editor();

			for (List<StringName>::Element *F=atlas_images.front();F;F=F->next()) {

				imd->add_source(EditorImportPlugin::validate_source_path(F->get()));
			}


			imd->set_option("format",group_format);


			int flags=0;

			if (Globals::get_singleton()->get("texture_import/filter"))
				flags|=EditorTextureImportPlugin::IMAGE_FLAG_FILTER;
			if (!Globals::get_singleton()->get("texture_import/gen_mipmaps"))
				flags|=EditorTextureImportPlugin::IMAGE_FLAG_NO_MIPMAPS;
			if (!Globals::get_singleton()->get("texture_import/repeat"))
				flags|=EditorTextureImportPlugin::IMAGE_FLAG_REPEAT;

			flags|=EditorTextureImportPlugin::IMAGE_FLAG_FIX_BORDER_ALPHA;

			imd->set_option("flags",flags);
			imd->set_option("quality",group_lossy_quality);
			imd->set_option("atlas",true);
			imd->set_option("crop",true);
			imd->set_option("shrink",group_shrink);



			Ref<EditorTextureImportPlugin> plugin = EditorImportExport::get_singleton()->get_import_plugin_by_name("texture_atlas");
			Error err = plugin->import2(dst_file,imd,get_image_compression(),true);
			if (err) {

				EditorNode::add_io_error("Error saving atlas! "+dst_file.get_file());
				return ERR_CANT_CREATE;
			}

			ERR_FAIL_COND_V(imd->get_option("rects")==Variant(),ERR_BUG);

			Array r_rects=imd->get_option("rects");
			rects.resize(r_rects.size());
			for(int i=0;i<r_rects.size();i++) {
				//get back region and margins

				rects[i]=r_rects[i];
			}


			resave_deps=true;
		}


		//atlas is valid (or it was just saved i guess), create the atex files and save them

		if (resave_deps) {
			f=FileAccess::open(EditorSettings::get_singleton()->get_settings_path()+"/tmp/atlas-"+md5,FileAccess::WRITE);
			Dictionary options;
			options["lossy_quality"]=group_lossy_quality;
			options["shrink"]=EditorImportExport::get_singleton()->image_export_group_get_shrink(E->get());
			options["image_format"]=group_format;
			f->store_line(options.to_json());
			f->store_line(image_list_md5);
		}

		//go through all ATEX files

		{
			Ref<ImageTexture> atlas = memnew( ImageTexture ); //fake atlas!
			String atlas_path="res://atlas-"+md5+".tex";
			atlas->set_path(atlas_path);
			int idx=0;
			for (List<StringName>::Element *F=atlas_images.front();F;F=F->next()) {

				String p = F->get();
				Ref<AtlasTexture> atex = memnew(AtlasTexture);
				atex->set_atlas(atlas);
				Rect2 region=rects[idx++];
				Rect2 margin=rects[idx++];
				atex->set_region(region);
				atex->set_margin(margin);

				String path = EditorSettings::get_singleton()->get_settings_path()+"/tmp/tmpatlas.atex";
				Error err = ResourceSaver::save(path,atex);
				if (err!=OK) {
					EditorNode::add_io_error("Could not save atlas subtexture: "+path);
					return ERR_CANT_CREATE;
				}
				Vector<uint8_t> data = FileAccess::get_file_as_array(path);
				String dst_path = F->get().operator String().basename()+".atex";
				err = p_func(p_udata,dst_path,data,counter++,files.size());
				saved.insert(dst_path);
				if (err)
					return err;

				if (f) {
					//recreating deps..
					String depline;
//					depline=String(F->get())+"::"+itos(FileAccess::get_modified_time(F->get()))+"::"+FileAccess::get_md5(F->get()); name unneccesary by top md5
					depline=itos(FileAccess::get_modified_time(F->get()))+"::"+FileAccess::get_md5(F->get());
					depline+="::"+itos(region.pos.x)+"::"+itos(region.pos.y)+"::"+itos(region.size.x)+"::"+itos(region.size.y);
					depline+="::"+itos(margin.pos.x)+"::"+itos(margin.pos.y)+"::"+itos(margin.size.x)+"::"+itos(margin.size.y);
					f->store_line(depline);
				}

				remap_files[F->get()]=dst_path;
			}

			Vector<uint8_t> atlas_data = FileAccess::get_file_as_array(EditorSettings::get_singleton()->get_settings_path()+"/tmp/atlas-"+md5+".tex");
			Error err = p_func(p_udata,atlas_path,atlas_data,counter,files.size());
			saved.insert(atlas_path);
			if (err)
				return err;

		}


		if (f) {
			memdelete(f);
		}

	}


	StringName engine_cfg="res://engine.cfg";

	for(int i=0;i<files.size();i++) {

		if (remap_files.has(files[i]) || files[i]==engine_cfg) //gonna be remapped (happened before!)
			continue; //from atlas?
		String src=files[i];
		Vector<uint8_t> buf = get_exported_file(src);

		ERR_CONTINUE( saved.has(src) );

		Error err = p_func(p_udata,src,buf,counter++,files.size());
		if (err)
			return err;

		saved.insert(src);
		if (src!=String(files[i]))
			remap_files[files[i]]=src;

	}


	{

		//make binary engine.cfg config

		Map<String,Variant> custom;

		if (remap_files.size()) {
			Vector<String> remapsprop;
			for(Map<StringName,StringName>::Element *E=remap_files.front();E;E=E->next()) {
				remapsprop.push_back(E->key());
				remapsprop.push_back(E->get());
			}

			custom["remap/all"]=remapsprop;
		}
		String remap_file="engine.cfb";
		String engine_cfb =EditorSettings::get_singleton()->get_settings_path()+"/tmp/tmp"+remap_file;
		Globals::get_singleton()->save_custom(engine_cfb,custom);
		Vector<uint8_t> data = FileAccess::get_file_as_array(engine_cfb);

		Error err = p_func(p_udata,"res://"+remap_file,data,counter,files.size());
		if (err)
			return err;

	}

	return OK;
}