/*************************************************************************/
/*  editor_sample_import_plugin.cpp                                      */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                    http://www.godotengine.org                         */
/*************************************************************************/
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                 */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the       */
/* "Software"), to deal in the Software without restriction, including   */
/* without limitation the rights to use, copy, modify, merge, publish,   */
/* distribute, sublicense, and/or sell copies of the Software, and to    */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions:                                             */
/*                                                                       */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/
#include "editor_sample_import_plugin.h"
#include "scene/gui/file_dialog.h"
#include "tools/editor/editor_dir_dialog.h"
#include "tools/editor/editor_node.h"
#include "tools/editor/property_editor.h"
#include "scene/resources/sample.h"
#include "io/resource_saver.h"
#include "os/file_access.h"
#include "io/marshalls.h"

class _EditorSampleImportOptions : public Object {

	OBJ_TYPE(_EditorSampleImportOptions,Object);
public:

	enum CompressBitrate {
		COMPRESS_64,
		COMPRESS_96,
		COMPRESS_128,
		COMPRESS_192
	};

	bool force_8_bit;
	bool force_mono;
	bool force_rate;
	float force_rate_hz;

	bool edit_trim;
	bool edit_normalize;
	bool edit_loop;

	bool compress_enable;
	CompressBitrate compress_bitrate;


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

		String n = p_name;
		if (n=="force/8_bit")
			force_8_bit=p_value;
		else if (n=="force/mono")
			force_mono=p_value;
		else if (n=="force/max_rate")
			force_rate=p_value;
		else if (n=="force/max_rate_hz")
			force_rate_hz=p_value;
		else if (n=="edit/trim")
			edit_trim=p_value;
		else if (n=="edit/normalize")
			edit_normalize=p_value;
		else if (n=="edit/loop")
			edit_loop=p_value;
		else if (n=="compress/enable")
			compress_enable=p_value;
		else if (n=="compress/bitrate")
			compress_bitrate=CompressBitrate(int(p_value));
		else
			return false;

		return true;

	}

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

		String n = p_name;
		if (n=="force/8_bit")
			r_ret=force_8_bit;
		else if (n=="force/mono")
			r_ret=force_mono;
		else if (n=="force/max_rate")
			r_ret=force_rate;
		else if (n=="force/max_rate_hz")
			r_ret=force_rate_hz;
		else if (n=="edit/trim")
			r_ret=edit_trim;
		else if (n=="edit/normalize")
			r_ret=edit_normalize;
		else if (n=="edit/loop")
			r_ret=edit_loop;
		else if (n=="compress/enable")
			r_ret=compress_enable;
		else if (n=="compress/bitrate")
			r_ret=compress_bitrate;
		else
			return false;

		return true;

	}
	void _get_property_list( List<PropertyInfo> *p_list) const{

		p_list->push_back(PropertyInfo(Variant::BOOL,"force/8_bit"));
		p_list->push_back(PropertyInfo(Variant::BOOL,"force/mono"));
		p_list->push_back(PropertyInfo(Variant::BOOL,"force/max_rate"));
		p_list->push_back(PropertyInfo(Variant::REAL,"force/max_rate_hz",PROPERTY_HINT_EXP_RANGE,"11025,192000,1"));
		p_list->push_back(PropertyInfo(Variant::BOOL,"edit/trim"));
		p_list->push_back(PropertyInfo(Variant::BOOL,"edit/normalize"));
		p_list->push_back(PropertyInfo(Variant::BOOL,"edit/loop"));
		//p_list->push_back(PropertyInfo(Variant::BOOL,"compress/enable"));
		//p_list->push_back(PropertyInfo(Variant::INT,"compress/bitrate",PROPERTY_HINT_ENUM,"64,96,128,192"));


	}


	static void _bind_methods() {


		ADD_SIGNAL( MethodInfo("changed"));
	}


	_EditorSampleImportOptions() {

		force_8_bit=false;
		force_mono=false;
		force_rate=true;
		force_rate_hz=44100;

		edit_trim=true;
		edit_normalize=true;
		edit_loop=false;

		compress_enable=false;
		compress_bitrate=COMPRESS_128;
	}


};

class EditorSampleImportDialog : public ConfirmationDialog {

	OBJ_TYPE(EditorSampleImportDialog,ConfirmationDialog);

	EditorSampleImportPlugin *plugin;

	LineEdit *import_path;
	LineEdit *save_path;
	FileDialog *file_select;
	EditorDirDialog *save_select;
	ConfirmationDialog *error_dialog;
	PropertyEditor *option_editor;

	_EditorSampleImportOptions *options;


public:

	void _choose_files(const Vector<String>& p_path) {

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

			if (i>0)
				files+=",";
			files+=p_path[i];
		}
		/*
		if (p_path.size()) {
			String srctex=p_path[0];
			String ipath = EditorImportDB::get_singleton()->find_source_path(srctex);

			if (ipath!="")
				save_path->set_text(ipath.get_base_dir());
		}*/
		import_path->set_text(files);

	}
	void _choose_save_dir(const String& p_path) {

		save_path->set_text(p_path);
	}

	void _browse() {

		file_select->popup_centered_ratio();
	}

	void _browse_target() {

		save_select->popup_centered_ratio();

	}


	void popup_import(const String& p_path) {

		popup_centered(Size2(400,400));
		if (p_path!="") {

			Ref<ResourceImportMetadata> rimd = ResourceLoader::load_import_metadata(p_path);
			ERR_FAIL_COND(!rimd.is_valid());

			save_path->set_text(p_path.get_base_dir());
			List<String> opts;
			rimd->get_options(&opts);
			for(List<String>::Element *E=opts.front();E;E=E->next()) {

				options->_set(E->get(),rimd->get_option(E->get()));
			}

			String src = "";
			for(int i=0;i<rimd->get_source_count();i++) {
				if (i>0)
					src+=",";
				src+=EditorImportPlugin::expand_source_path(rimd->get_source_path(i));
			}
			import_path->set_text(src);
		}
	}


	void _import() {

		Vector<String> samples = import_path->get_text().split(",");

		if (samples.size()==0) {
			error_dialog->set_text("No samples to import!");
			error_dialog->popup_centered(Size2(200,100));
		}

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

			Ref<ResourceImportMetadata> imd = memnew( ResourceImportMetadata );

			List<PropertyInfo> pl;
			options->_get_property_list(&pl);
			for(List<PropertyInfo>::Element *E=pl.front();E;E=E->next()) {

				Variant v;
				String opt=E->get().name;
				options->_get(opt,v);
				imd->set_option(opt,v);

			}

			imd->add_source(EditorImportPlugin::validate_source_path(samples[i]));

			String dst = save_path->get_text();
			if (dst=="") {
				error_dialog->set_text("Save path is empty!");
				error_dialog->popup_centered(Size2(200,100));
			}

			dst = dst.plus_file(samples[i].get_file().basename()+".smp");

			Error err = plugin->import(dst,imd);
		}

		hide();

	}


	void _notification(int p_what) {


		if (p_what==NOTIFICATION_ENTER_SCENE) {

			option_editor->edit(options);
		}
	}

	static void _bind_methods() {


		ObjectTypeDB::bind_method("_choose_files",&EditorSampleImportDialog::_choose_files);
		ObjectTypeDB::bind_method("_choose_save_dir",&EditorSampleImportDialog::_choose_save_dir);
		ObjectTypeDB::bind_method("_import",&EditorSampleImportDialog::_import);
		ObjectTypeDB::bind_method("_browse",&EditorSampleImportDialog::_browse);
		ObjectTypeDB::bind_method("_browse_target",&EditorSampleImportDialog::_browse_target);
	//	ADD_SIGNAL( MethodInfo("imported",PropertyInfo(Variant::OBJECT,"scene")) );
	}

	EditorSampleImportDialog(EditorSampleImportPlugin *p_plugin) {

		plugin=p_plugin;


		set_title("Import Audio Samples");

		VBoxContainer *vbc = memnew( VBoxContainer );
		add_child(vbc);
		set_child_rect(vbc);


		HBoxContainer *hbc = memnew( HBoxContainer );
		vbc->add_margin_child("Source Sample(s):",hbc);

		import_path = memnew( LineEdit );
		import_path->set_h_size_flags(SIZE_EXPAND_FILL);
		hbc->add_child(import_path);

		Button * import_choose = memnew( Button );
		import_choose->set_text(" .. ");
		hbc->add_child(import_choose);

		import_choose->connect("pressed", this,"_browse");

		hbc = memnew( HBoxContainer );
		vbc->add_margin_child("Target Path:",hbc);

		save_path = memnew( LineEdit );
		save_path->set_h_size_flags(SIZE_EXPAND_FILL);
		hbc->add_child(save_path);

		Button * save_choose = memnew( Button );
		save_choose->set_text(" .. ");
		hbc->add_child(save_choose);

		save_choose->connect("pressed", this,"_browse_target");

		file_select = memnew(FileDialog);
		file_select->set_access(FileDialog::ACCESS_FILESYSTEM);
		add_child(file_select);
		file_select->set_mode(FileDialog::MODE_OPEN_FILES);
		file_select->connect("files_selected", this,"_choose_files");
		file_select->add_filter("*.wav ; MS Waveform");
		save_select = memnew(	EditorDirDialog );
		add_child(save_select);

	//	save_select->set_mode(FileDialog::MODE_OPEN_DIR);
		save_select->connect("dir_selected", this,"_choose_save_dir");

		get_ok()->connect("pressed", this,"_import");
		get_ok()->set_text("Import");


		error_dialog = memnew ( ConfirmationDialog );
		add_child(error_dialog);
		error_dialog->get_ok()->set_text("Accept");
	//	error_dialog->get_cancel()->hide();

		set_hide_on_ok(false);
		options = memnew( _EditorSampleImportOptions );

		option_editor = memnew( PropertyEditor );
		option_editor->hide_top_label();
		vbc->add_margin_child("Options:",option_editor,true);
	}

	~EditorSampleImportDialog() {
		memdelete(options);
	}

};


String EditorSampleImportPlugin::get_name() const {

	return "sample";
}
String EditorSampleImportPlugin::get_visible_name() const{

	return "Audio Sample";
}
void EditorSampleImportPlugin::import_dialog(const String& p_from){

	dialog->popup_import(p_from);
}
Error EditorSampleImportPlugin::import(const String& p_path, const Ref<ResourceImportMetadata>& p_from){

	ERR_FAIL_COND_V(p_from->get_source_count()!=1,ERR_INVALID_PARAMETER);

	Ref<ResourceImportMetadata> from=p_from;

	String src_path=EditorImportPlugin::expand_source_path(from->get_source_path(0));
	Ref<Sample> smp = ResourceLoader::load(src_path);
	ERR_FAIL_COND_V(smp.is_null(),ERR_CANT_OPEN);


	float rate = smp->get_mix_rate();
	bool is16 = smp->get_format()==Sample::FORMAT_PCM16;
	int chans = smp->is_stereo()?2:1;
	int len = smp->get_length();
	Sample::LoopFormat loop= smp->get_loop_format();
	int loop_beg = smp->get_loop_begin();
	int loop_end = smp->get_loop_end();

	print_line("Input Sample: ");
	print_line("\tlen: "+itos(len));
	print_line("\tchans: "+itos(chans));
	print_line("\t16bits: "+itos(is16));
	print_line("\trate: "+itos(rate));
	print_line("\tloop: "+itos(loop));
	print_line("\tloop begin: "+itos(loop_beg));
	print_line("\tloop end: "+itos(loop_end));
	Vector<float> data;
	data.resize(len*chans);

	{
		DVector<uint8_t> src_data = smp->get_data();
		DVector<uint8_t>::Read sr = src_data.read();


		for(int i=0;i<len*chans;i++) {

			float s=0;
			if (is16) {

				int16_t i16 = decode_uint16(&sr[i*2]);
				s=i16/32767.0;
			} else {

				int8_t i8 = sr[i];
				s=i8/127.0;
			}
			data[i]=s;
		}
	}

	//apply frequency limit

	bool limit_rate = from->get_option("force/max_rate");
	int limit_rate_hz = from->get_option("force/max_rate_hz");
	if (limit_rate && rate > limit_rate_hz) {
		//resampleeee!!!
		int new_data_len = len * limit_rate_hz / rate;
		Vector<float> new_data;
		new_data.resize( new_data_len * chans );
		for(int c=0;c<chans;c++) {

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

				//simple cubic interpolation should be enough.
				float pos = float(i) * len / new_data_len;
				float mu = pos-Math::floor(pos);
				int ipos = int(Math::floor(pos));

				float y0=data[MAX(0,ipos-i)];
				float y1=data[ipos];
				float y2=data[MIN(len-1,ipos+1)];
				float y3=data[MIN(len-1,ipos+2)];

				float mu2 = mu*mu;
				float a0 = y3 - y2 - y0 + y1;
				float a1 = y0 - y1 - a0;
				float a2 = y2 - y0;
				float a3 = y1;

				float res=(a0*mu*mu2+a1*mu2+a2*mu+a3);

				new_data[i*chans+c]=res;
			}
		}

		if (loop) {

			loop_beg=loop_beg*new_data_len/len;
			loop_end=loop_end*new_data_len/len;
		}
		data=new_data;
		rate=limit_rate_hz;
		len=new_data_len;
	}


	bool normalize = from->get_option("edit/normalize");

	if (normalize) {

		float max=0;
		for(int i=0;i<data.size();i++) {

			float amp = Math::abs(data[i]);
			if (amp>max)
				max=amp;
		}

		if (max>0) {

			float mult=1.0/max;
			for(int i=0;i<data.size();i++) {

				data[i]*=mult;
			}

		}
	}

	bool trim = from->get_option("edit/trim");

	if (trim && !loop) {

		int first=0;
		int last=(len*chans)-1;
		bool found=false;
		float limit = Math::db2linear(-30);
		for(int i=0;i<data.size();i++) {
			float amp = Math::abs(data[i]);

			if (!found && amp > limit) {
				first=i;
				found=true;
			}

			if (found && amp > limit) {
				last=i;
			}
		}

		first/=chans;
		last/=chans;

		if (first<last) {

			Vector<float> new_data;
			new_data.resize((last-first+1)*chans);
			for(int i=first*chans;i<=last*chans;i++) {
				new_data[i-first*chans]=data[i];
			}

			data=new_data;
			len=data.size()/chans;
		}

	}

	bool make_loop = from->get_option("edit/loop");

	if (make_loop && !loop) {

		loop=Sample::LOOP_FORWARD;
		loop_beg=0;
		loop_end=len;
	}

	bool force_mono = from->get_option("force/mono");
	if (force_mono && chans==2) {

		Vector<float> new_data;
		new_data.resize(data.size()/2);
		for(int i=0;i<len;i++) {
			new_data[i]=(data[i*2+0]+data[i*2+1])/2.0;
		}

		data=new_data;
		chans=1;
	}

	bool force_8_bit = from->get_option("force/8_bit");
	if (force_8_bit) {

		is16=false;
	}


	DVector<uint8_t> dst_data;
	dst_data.resize( data.size() * (is16?2:1));
	{
		DVector<uint8_t>::Write w = dst_data.write();

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

			if (is16) {
				int16_t v = CLAMP(data[i]*32767,-32768,32767);
				encode_uint16(v,&w[i*2]);
			} else {
				int8_t v = CLAMP(data[i]*127,-128,127);
				w[i]=v;
			}
		}
	}


	Ref<Sample> target;

	if (ResourceCache::has(p_path)) {

		target = Ref<Sample>( ResourceCache::get(p_path)->cast_to<Sample>() );
	} else {

		target = smp;
	}

	target->create(is16?Sample::FORMAT_PCM16:Sample::FORMAT_PCM8,chans==2?true:false,len);
	target->set_data(dst_data);
	target->set_mix_rate(rate);
	target->set_loop_format(loop);
	target->set_loop_begin(loop_beg);
	target->set_loop_end(loop_end);

	from->set_source_md5(0,FileAccess::get_md5(src_path));
	from->set_editor(get_name());
	target->set_import_metadata(from);


	Error err = ResourceSaver::save(p_path,smp);

	return err;

}


EditorSampleImportPlugin::EditorSampleImportPlugin(EditorNode* p_editor) {

	dialog = memnew( EditorSampleImportDialog(this));
	p_editor->get_gui_base()->add_child(dialog);
}

