#include "version.h"
#include "export.h"
#include "tools/editor/editor_settings.h"
#include "tools/editor/editor_import_export.h"
#include "tools/editor/editor_node.h"
#include "io/zip_io.h"
#include "io/marshalls.h"
#include "globals.h"
#include "os/file_access.h"
#include "os/os.h"
#include "platform/bb10/logo.h"
#include "io/xml_parser.h"

#define MAX_DEVICES 5

class EditorExportPlatformBB10 : public EditorExportPlatform {

	OBJ_TYPE( EditorExportPlatformBB10,EditorExportPlatform );

	String custom_package;

	int version_code;
	String version_name;
	String package;
	String name;
	String category;
	String description;
	String author_name;
	String author_id;
	String icon;



	struct Device {

		int index;
		String name;
		String description;
	};

	Vector<Device> devices;
	bool devices_changed;
	Mutex *device_lock;
	Thread *device_thread;
	Ref<ImageTexture> logo;

	volatile bool quit_request;


	static void _device_poll_thread(void *ud);

	void _fix_descriptor(Vector<uint8_t>& p_manifest);
protected:

	bool _set(const StringName& p_name, const Variant& p_value);
	bool _get(const StringName& p_name,Variant &r_ret) const;
	void _get_property_list( List<PropertyInfo> *p_list) const;

public:

	virtual String get_name() const { return "BlackBerry 10"; }
	virtual ImageCompression get_image_compression() const { return IMAGE_COMPRESSION_ETC1; }
	virtual Ref<Texture> get_logo() const { return logo; }


	virtual bool poll_devices();
	virtual int get_device_count() const;
	virtual String get_device_name(int p_device) const;
	virtual String get_device_info(int p_device) const;
	virtual Error run(int p_device);

	virtual bool requieres_password(bool p_debug) const { return !p_debug; }
	virtual String get_binary_extension() const { return "bar"; }
	virtual Error export_project(const String& p_path,bool p_debug,const String& p_password="");

	virtual bool can_export(String *r_error=NULL) const;

	EditorExportPlatformBB10();
	~EditorExportPlatformBB10();
};

bool EditorExportPlatformBB10::_set(const StringName& p_name, const Variant& p_value) {

	String n=p_name;

	if (n=="version/code")
		version_code=p_value;
	else if (n=="version/name")
		version_name=p_value;
	else if (n=="package/unique_name")
		package=p_value;
	else if (n=="package/category")
		category=p_value;
	else if (n=="package/name")
		name=p_value;
	else if (n=="package/description")
		description=p_value;
	else if (n=="package/icon")
		icon=p_value;
	else if (n=="package/custom_template")
		custom_package=p_value;
	else if (n=="release/author")
		author_name=p_value;
	else if (n=="release/author_id")
		author_id=p_value;
	else
		return false;

	return true;
}

bool EditorExportPlatformBB10::_get(const StringName& p_name,Variant &r_ret) const{

	String n=p_name;

	if (n=="version/code")
		r_ret=version_code;
	else if (n=="version/name")
		r_ret=version_name;
	else if (n=="package/unique_name")
		r_ret=package;
	else if (n=="package/category")
		r_ret=category;
	else if (n=="package/name")
		r_ret=name;
	else if (n=="package/description")
		r_ret=description;
	else if (n=="package/icon")
		r_ret=icon;
	else if (n=="package/custom_template")
		r_ret=custom_package;
	else if (n=="release/author")
		r_ret=author_name;
	else if (n=="release/author_id")
		r_ret=author_id;
	else
		return false;

	return true;
}
void EditorExportPlatformBB10::_get_property_list( List<PropertyInfo> *p_list) const{

	p_list->push_back( PropertyInfo( Variant::INT, "version/code", PROPERTY_HINT_RANGE,"1,65535,1"));
	p_list->push_back( PropertyInfo( Variant::STRING, "version/name") );
	p_list->push_back( PropertyInfo( Variant::STRING, "package/unique_name") );
	p_list->push_back( PropertyInfo( Variant::STRING, "package/category") );
	p_list->push_back( PropertyInfo( Variant::STRING, "package/name") );
	p_list->push_back( PropertyInfo( Variant::STRING, "package/description",PROPERTY_HINT_MULTILINE_TEXT) );
	p_list->push_back( PropertyInfo( Variant::STRING, "package/icon",PROPERTY_HINT_FILE,"png") );
	p_list->push_back( PropertyInfo( Variant::STRING, "package/custom_template", PROPERTY_HINT_FILE,"zip"));
	p_list->push_back( PropertyInfo( Variant::STRING, "release/author") );
	p_list->push_back( PropertyInfo( Variant::STRING, "release/author_id") );

	//p_list->push_back( PropertyInfo( Variant::INT, "resources/pack_mode", PROPERTY_HINT_ENUM,"Copy,Single Exec.,Pack (.pck),Bundles (Optical)"));

}

void EditorExportPlatformBB10::_fix_descriptor(Vector<uint8_t>& p_descriptor) {

	String fpath =  EditorSettings::get_singleton()->get_settings_path().plus_file("tmp_bar-settings.xml");
	{
		FileAccessRef f = FileAccess::open(fpath,FileAccess::WRITE);
		f->store_buffer(p_descriptor.ptr(),p_descriptor.size());
	}

	Ref<XMLParser> parser = memnew( XMLParser );
	Error err = parser->open(fpath);
	ERR_FAIL_COND(err!=OK);

	String txt;
	err = parser->read();
	Vector<String> depth;

	while(err!=ERR_FILE_EOF) {

		ERR_FAIL_COND(err!=OK);

		switch(parser->get_node_type()) {

			case XMLParser::NODE_NONE: {
				print_line("???");
			} break;
			case XMLParser::NODE_ELEMENT: {
				String e="<";
				e+=parser->get_node_name();
				for(int i=0;i<parser->get_attribute_count();i++) {
					e+=" ";
					e+=parser->get_attribute_name(i)+"=\"";
					e+=parser->get_attribute_value(i)+"\" ";
				}



				if (parser->is_empty()) {
					e+="/";
				} else {
					depth.push_back(parser->get_node_name());
				}

				e+=">";
				txt+=e;

			} break;
			case XMLParser::NODE_ELEMENT_END: {

				txt+="</"+parser->get_node_name()+">";
				if (depth.size() && depth[depth.size()-1]==parser->get_node_name()) {
					depth.resize(depth.size()-1);
				}


			} break;
			case XMLParser::NODE_TEXT: {
				if (depth.size()==2 && depth[0]=="qnx" && depth[1]=="id") {

					txt+=package;
				} else if (depth.size()==2 && depth[0]=="qnx" && depth[1]=="name") {

					String aname;
					if (this->name!="") {
						aname=this->name;
					} else {
						aname = Globals::get_singleton()->get("application/name");

					}

					if (aname=="") {
						aname=_MKSTR(VERSION_NAME);
					}

					txt+=aname;

				} else if (depth.size()==2 && depth[0]=="qnx" && depth[1]=="versionNumber") {
					txt+=itos(version_code);
				} else if (depth.size()==2 && depth[0]=="qnx" && depth[1]=="description") {
					txt+=description;
				} else if (depth.size()==2 && depth[0]=="qnx" && depth[1]=="author") {
					txt+=author_name;
				} else if (depth.size()==2 && depth[0]=="qnx" && depth[1]=="authorId") {
					txt+=author_id;
				} else if (depth.size()==2 && depth[0]=="qnx" && depth[1]=="category") {
					txt+=category;
				} else {
					txt+=parser->get_node_data();
				}
			} break;
			case XMLParser::NODE_COMMENT: {
				txt+="<!--"+parser->get_node_name()+"-->";
			} break;
			case XMLParser::NODE_CDATA: {
				//ignore
				//print_line("cdata");
			} break;
			case XMLParser::NODE_UNKNOWN: {
				//ignore
				txt+="<"+parser->get_node_name()+">";
			} break;
		}

		err = parser->read();
	}


	CharString cs = txt.utf8();
	p_descriptor.resize(cs.length());
	for(int i=0;i<cs.length();i++)
		p_descriptor[i]=cs[i];

}



Error EditorExportPlatformBB10::export_project(const String& p_path,bool p_debug,const String& p_password) {


	EditorProgress ep("export","Exporting for BlackBerry 10",104);

	String template_path = EditorSettings::get_singleton()->get_settings_path()+"/templates/";

	String src_template=custom_package!=""?custom_package:template_path.plus_file("bb10.zip");


	FileAccess *src_f=NULL;
	zlib_filefunc_def io = zipio_create_io_from_file(&src_f);

	ep.step("Creating FileSystem for BAR",0);

	unzFile pkg = unzOpen2(src_template.utf8().get_data(), &io);
	if (!pkg) {

		EditorNode::add_io_error("Could not find template zip to export:\n"+src_template);
		return ERR_FILE_NOT_FOUND;
	}

	DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
	da->change_dir(EditorSettings::get_singleton()->get_settings_path());


	if (da->change_dir("tmp")!=OK) {
		da->make_dir("tmp");
		if (da->change_dir("tmp")!=OK)
			return ERR_CANT_CREATE;
	}

	if (da->change_dir("bb10_export")!=OK) {
		da->make_dir("bb10_export");
		if (da->change_dir("bb10_export")!=OK) {
			return ERR_CANT_CREATE;
		}
	}


	String bar_dir = da->get_current_dir();
	if (bar_dir.ends_with("/")) {
		bar_dir=bar_dir.substr(0,bar_dir.length()-1);
	}

	//THIS IS SUPER, SUPER DANGEROUS!!!!
	//CAREFUL WITH THIS CODE, MIGHT DELETE USERS HARD DRIVE OR HOME DIR
	//EXTRA CHECKS ARE IN PLACE EVERYWERE TO MAKE SURE NOTHING BAD HAPPENS BUT STILL....
	//BE SUPER CAREFUL WITH THIS PLEASE!!!
	//BLACKBERRY THIS IS YOUR FAULT FOR NOT MAKING A BETTER WAY!!

	if (bar_dir.ends_with("bb10_export")) {
		Error err = da->erase_contents_recursive();
		if (err!=OK) {
			EditorNode::add_io_error("Can't ensure that dir is empty:\n"+bar_dir);
			ERR_FAIL_COND_V(err!=OK,err);
		}

	} else {
		print_line("ARE YOU CRAZY??? THIS IS A SERIOUS BUG HERE!!!");
		ERR_FAIL_V(ERR_OMFG_THIS_IS_VERY_VERY_BAD);
	}


	ERR_FAIL_COND_V(!pkg, ERR_CANT_OPEN);
	int ret = unzGoToFirstFile(pkg);



	while(ret==UNZ_OK) {

		//get filename
		unz_file_info info;
		char fname[16384];
		ret = unzGetCurrentFileInfo(pkg,&info,fname,16384,NULL,0,NULL,0);

		String file=fname;

		Vector<uint8_t> data;
		data.resize(info.uncompressed_size);

		//read
		unzOpenCurrentFile(pkg);
		unzReadCurrentFile(pkg,data.ptr(),data.size());
		unzCloseCurrentFile(pkg);

		//write

		if (file=="bar-descriptor.xml") {

			_fix_descriptor(data);
		}

		if (file=="icon.png") {
			bool found=false;

			if (this->icon!="" && this->icon.ends_with(".png")) {

				FileAccess *f = FileAccess::open(this->icon,FileAccess::READ);
				if (f) {

					data.resize(f->get_len());
					f->get_buffer(data.ptr(),data.size());
					memdelete(f);
					found=true;
				}

			}

			if (!found) {

				String appicon = Globals::get_singleton()->get("application/icon");
				if (appicon!="" && appicon.ends_with(".png")) {
					FileAccess*f = FileAccess::open(appicon,FileAccess::READ);
					if (f) {
						data.resize(f->get_len());
						f->get_buffer(data.ptr(),data.size());
						memdelete(f);
					}
				}
			}
		}


		if (file.find("/")) {

			da->make_dir_recursive(file.get_base_dir());
		}

		FileAccessRef wf = FileAccess::open(bar_dir.plus_file(file),FileAccess::WRITE);
		wf->store_buffer(data.ptr(),data.size());

		ret = unzGoToNextFile(pkg);
	}

	ep.step("Finding Files..",1);

	Vector<StringName> files=get_dependencies(false);

	ep.step("Adding Files..",2);

	da->change_dir(bar_dir);
	da->make_dir("assets");
	Error err = da->change_dir("assets");
	ERR_FAIL_COND_V(err,err);

	String asset_dir=da->get_current_dir();
	if (!asset_dir.ends_with("/"))
		asset_dir+="/";

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

		String fname=files[i];
		Vector<uint8_t> data = get_exported_file(fname);
		/*
		FileAccess *f=FileAccess::open(files[i],FileAccess::READ);
		if (!f) {
			EditorNode::add_io_error("Couldn't read: "+String(files[i]));
		}
		ERR_CONTINUE(!f);
		data.resize(f->get_len());
		f->get_buffer(data.ptr(),data.size());
*/
		String dst_path=fname;
		dst_path=dst_path.replace_first("res://",asset_dir);

		da->make_dir_recursive(dst_path.get_base_dir());

		ep.step("Adding File: "+String(files[i]).get_file(),3+i*100/files.size());

		FileAccessRef fr = FileAccess::open(dst_path,FileAccess::WRITE);
		fr->store_buffer(data.ptr(),data.size());
	}


	ep.step("Creating BAR Package..",104);

	String bb_packager=EditorSettings::get_singleton()->get("blackberry/host_tools");
	bb_packager=bb_packager.plus_file("blackberry-nativepackager");
	if (OS::get_singleton()->get_name()=="Windows")
		bb_packager+=".exe";


	if (!FileAccess::exists(bb_packager)) {
		EditorNode::add_io_error("Can't find packager:\n"+bb_packager);
		return ERR_CANT_OPEN;
	}

	List<String> args;
	args.push_back("-package");
	args.push_back(p_path);
	if (p_debug) {

		String debug_token=EditorSettings::get_singleton()->get("blackberry/debug_token");
		if (!FileAccess::exists(debug_token)) {
			EditorNode::add_io_error("Debug token not found!");
		} else {
			args.push_back("-debugToken");
			args.push_back(debug_token);
		}
		args.push_back("-devMode");
		args.push_back("-configuration");
		args.push_back("Device-Debug");
	} else {

		args.push_back("-configuration");
		args.push_back("Device-Release");
	}
	args.push_back(bar_dir.plus_file("bar-descriptor.xml"));

	int ec;

	err = OS::get_singleton()->execute(bb_packager,args,true,NULL,NULL,&ec);

	if (err!=OK)
		return err;
	if (ec!=0)
		return ERR_CANT_CREATE;
	
	return OK;

}


bool EditorExportPlatformBB10::poll_devices() {

	bool dc=devices_changed;
	devices_changed=false;
	return dc;
}

int EditorExportPlatformBB10::get_device_count() const {

	device_lock->lock();
	int dc=devices.size();
	device_lock->unlock();

	return dc;

}
String EditorExportPlatformBB10::get_device_name(int p_device) const {

	ERR_FAIL_INDEX_V(p_device,devices.size(),"");
	device_lock->lock();
	String s=devices[p_device].name;
	device_lock->unlock();
	return s;
}
String EditorExportPlatformBB10::get_device_info(int p_device) const {

	ERR_FAIL_INDEX_V(p_device,devices.size(),"");
	device_lock->lock();
	String s=devices[p_device].description;
	device_lock->unlock();
	return s;
}

void EditorExportPlatformBB10::_device_poll_thread(void *ud) {

	EditorExportPlatformBB10 *ea=(EditorExportPlatformBB10 *)ud;

	while(!ea->quit_request) {

		String bb_deploy=EditorSettings::get_singleton()->get("blackberry/host_tools");
		bb_deploy=bb_deploy.plus_file("blackberry-deploy");
		bool windows = OS::get_singleton()->get_name()=="Windows";
		if (windows)
			bb_deploy+=".exe";

		if (!FileAccess::exists(bb_deploy)) {
			OS::get_singleton()->delay_usec(3000000);
			continue; //adb not configured
		}

		Vector<Device> devices;


		for (int i=0;i<MAX_DEVICES;i++) {

			String host = EditorSettings::get_singleton()->get("blackberry/device_"+itos(i+1)+"/host");
			if (host==String())
				continue;
			String pass = EditorSettings::get_singleton()->get("blackberry/device_"+itos(i+1)+"/password");
			if (pass==String())
				continue;

			List<String> args;
			args.push_back("-listDeviceInfo");
			args.push_back(host);
			args.push_back("-password");
			args.push_back(pass);


			int ec;
			String dp;

			Error err = OS::get_singleton()->execute(bb_deploy,args,true,NULL,&dp,&ec);

			if (err==OK && ec==0) {

				Device dev;
				dev.index=i;
				String descr;
				Vector<String> ls=dp.split("\n");

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

					String l = ls[i].strip_edges();
					if (l.begins_with("modelfullname::")) {
						dev.name=l.get_slice("::",1);
						descr+="Model: "+dev.name+"\n";
					}
					if (l.begins_with("modelnumber::")) {
						String s = l.get_slice("::",1);
						dev.name+=" ("+s+")";
						descr+="Model Number: "+s+"\n";
					}
					if (l.begins_with("scmbundle::"))
						descr+="OS Version: "+l.get_slice("::",1)+"\n";
					if (l.begins_with("[n]debug_token_expiration::"))
						descr+="Debug Token Expires:: "+l.get_slice("::",1)+"\n";

				}

				dev.description=descr;
				devices.push_back(dev);
			}

		}

		bool changed=false;


		ea->device_lock->lock();

		if (ea->devices.size()!=devices.size()) {
			changed=true;
		} else {

			for(int i=0;i<ea->devices.size();i++) {

				if (ea->devices[i].index!=devices[i].index) {
					changed=true;
					break;
				}
			}
		}

		if (changed) {

			ea->devices=devices;
			ea->devices_changed=true;
		}

		ea->device_lock->unlock();

		OS::get_singleton()->delay_usec(3000000);

	}

}

Error EditorExportPlatformBB10::run(int p_device) {

	ERR_FAIL_INDEX_V(p_device,devices.size(),ERR_INVALID_PARAMETER);

	String bb_deploy=EditorSettings::get_singleton()->get("blackberry/host_tools");
	bb_deploy=bb_deploy.plus_file("blackberry-deploy");
	if (OS::get_singleton()->get_name()=="Windows")
		bb_deploy+=".exe";

	if (!FileAccess::exists(bb_deploy)) {
		EditorNode::add_io_error("Blackberry Deploy not found:\n"+bb_deploy);
		return ERR_FILE_NOT_FOUND;
	}


	device_lock->lock();


	EditorProgress ep("run","Running on "+devices[p_device].name,3);

	//export_temp
	ep.step("Exporting APK",0);

	String export_to=EditorSettings::get_singleton()->get_settings_path().plus_file("/tmp/tmpexport.bar");
	Error err = export_project(export_to,true);
	if (err) {
		device_lock->unlock();
		return err;
	}
#if 0
	ep.step("Uninstalling..",1);

	print_line("Uninstalling previous version: "+devices[p_device].name);
	List<String> args;
	args.push_back("-s");
	args.push_back(devices[p_device].id);
	args.push_back("uninstall");
	args.push_back(package);
	int rv;
	err = OS::get_singleton()->execute(adb,args,true,NULL,NULL,&rv);

	if (err || rv!=0) {
		EditorNode::add_io_error("Could not install to device.");
		device_lock->unlock();
		return ERR_CANT_CREATE;
	}

	print_line("Installing into device (please wait..): "+devices[p_device].name);

#endif
	ep.step("Installing to Device (please wait..)..",2);

	List<String> args;
	args.clear();
	args.push_back("-installApp");
	args.push_back("-launchApp");
	args.push_back("-device");
	int idx = devices[p_device].index;
	String host = EditorSettings::get_singleton()->get("blackberry/device_"+itos(p_device+1)+"/host");
	String pass = EditorSettings::get_singleton()->get("blackberry/device_"+itos(p_device+1)+"/password");
	args.push_back(host);
	args.push_back("-password");
	args.push_back(pass);
	args.push_back(export_to);

	int rv;
	err = OS::get_singleton()->execute(bb_deploy,args,true,NULL,NULL,&rv);
	if (err || rv!=0) {
		EditorNode::add_io_error("Could not install to device.");
		device_lock->unlock();
		return ERR_CANT_CREATE;
	}

	device_lock->unlock();
	return OK;


}


EditorExportPlatformBB10::EditorExportPlatformBB10() {

	version_code=1;
	version_name="1.0";
	package="com.godot.noname";
	category="core.games";
	name="";
	author_name="Cert. Name";
	author_id="Cert. ID";
	description="Game made with Godot Engine";

	device_lock = Mutex::create();
	quit_request=false;

	device_thread=Thread::create(_device_poll_thread,this);
	devices_changed=true;

	Image img( _bb10_logo );
	logo = Ref<ImageTexture>( memnew( ImageTexture ));
	logo->create_from_image(img);
}

bool EditorExportPlatformBB10::can_export(String *r_error) const {

	bool valid=true;
	String bb_deploy=EditorSettings::get_singleton()->get("blackberry/host_tools");
	String err;

	if (!FileAccess::exists(bb_deploy.plus_file("blackberry-deploy"))) {

		valid=false;
		err+="Blackberry host tools not configured in editor settings.\n";
	}

	String exe_path = EditorSettings::get_singleton()->get_settings_path()+"/templates/";

	if (!FileAccess::exists(exe_path+"bb10.zip")) {
		valid=false;
		err+="No export template found.\nDownload and install export templates.\n";
	}

	String debug_token=EditorSettings::get_singleton()->get("blackberry/debug_token");

	if (!FileAccess::exists(debug_token)) {
		valid=false;
		err+="No debug token set, will not be able to test on device.\n";
	}


	if (custom_package!="" && !FileAccess::exists(custom_package)) {
		valid=false;
		err+="Custom release package not found.\n";
	}

	if (r_error)
		*r_error=err;

	return valid;
}


EditorExportPlatformBB10::~EditorExportPlatformBB10() {

	quit_request=true;
	Thread::wait_to_finish(device_thread);
}


void register_bb10_exporter() {

	EDITOR_DEF("blackberry/host_tools","");
	EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING,"blackberry/host_tools",PROPERTY_HINT_GLOBAL_DIR));
	EDITOR_DEF("blackberry/debug_token","");
	EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING,"blackberry/debug_token",PROPERTY_HINT_GLOBAL_FILE,"bar"));
	EDITOR_DEF("blackberry/device_1/host","");
	EDITOR_DEF("blackberry/device_1/password","");
	EDITOR_DEF("blackberry/device_2/host","");
	EDITOR_DEF("blackberry/device_2/password","");
	EDITOR_DEF("blackberry/device_3/host","");
	EDITOR_DEF("blackberry/device_3/password","");
	EDITOR_DEF("blackberry/device_4/host","");
	EDITOR_DEF("blackberry/device_4/password","");
	EDITOR_DEF("blackberry/device_5/host","");
	EDITOR_DEF("blackberry/device_5/password","");

	Ref<EditorExportPlatformBB10> exporter = Ref<EditorExportPlatformBB10>( memnew(EditorExportPlatformBB10) );
	EditorImportExport::get_singleton()->add_export_platform(exporter);


}

