#include "foobar2000.h"


bool input_entry::g_find_service_by_path(service_ptr_t<input_entry> & p_out,const char * p_path)
{
	service_ptr_t<input_entry> ptr;
	service_enum_t<input_entry> e;
	pfc::string_extension ext(p_path);
	while(e.next(ptr))
	{
		if (ptr->is_our_path(p_path,ext))
		{
			p_out = ptr;
			return true;
		}
	}
	return false;
}

bool input_entry::g_find_service_by_content_type(service_ptr_t<input_entry> & p_out,const char * p_content_type)
{
	service_ptr_t<input_entry> ptr;
	service_enum_t<input_entry> e;
	while(e.next(ptr))
	{
		if (ptr->is_our_content_type(p_content_type))
		{
			p_out = ptr;
			return true;
		}
	}
	return false;
}



static void prepare_for_open(service_ptr_t<input_entry> & p_service,service_ptr_t<file> & p_file,const char * p_path,filesystem::t_open_mode p_open_mode,abort_callback & p_abort,bool p_from_redirect)
{
	if (p_file.is_empty())
	{
		service_ptr_t<filesystem> fs;
		if (filesystem::g_get_interface(fs,p_path)) {
			if (fs->supports_content_types()) {
				fs->open(p_file,p_path,p_open_mode,p_abort);
			}
		}
	}

	if (p_file.is_valid())
	{
		pfc::string8 content_type;
		if (p_file->get_content_type(content_type))
		{
			if (input_entry::g_find_service_by_content_type(p_service,content_type))
				return;
		}
	}

	if (input_entry::g_find_service_by_path(p_service,p_path))
	{
		if (p_from_redirect && p_service->is_redirect()) throw exception_io_unsupported_format();
		return;
	}

	throw exception_io_unsupported_format();
}

namespace {

	bool g_find_inputs_by_content_type(pfc::list_base_t<service_ptr_t<input_entry> > & p_out,const char * p_content_type,bool p_from_redirect) {
		service_enum_t<input_entry> e;
		service_ptr_t<input_entry> ptr;
		bool ret = false;
		while(e.next(ptr)) {
			if (!(p_from_redirect && ptr->is_redirect())) {
				if (ptr->is_our_content_type(p_content_type)) {p_out.add_item(ptr); ret = true;}
			}
		}
		return ret;
	}

	bool g_find_inputs_by_path(pfc::list_base_t<service_ptr_t<input_entry> > & p_out,const char * p_path,bool p_from_redirect) {
		service_enum_t<input_entry> e;
		service_ptr_t<input_entry> ptr;
		pfc::string_extension extension(p_path);
		bool ret = false;
		while(e.next(ptr)) {
			if (!(p_from_redirect && ptr->is_redirect())) {
				if (ptr->is_our_path(p_path,extension)) {p_out.add_item(ptr); ret = true;}
			}
		}
		return ret;
	}

	template<typename t_service> void g_open_from_list(service_ptr_t<t_service> & p_instance,pfc::list_base_const_t<service_ptr_t<input_entry> > const & p_list,service_ptr_t<file> const & p_filehint,const char * p_path,abort_callback & p_abort) {
		const t_size count = p_list.get_count();
		if (count == 1) {
			p_list[0]->open(p_instance,p_filehint,p_path,p_abort);
		} else {
			bool got_bad_data = false, got_bad_data_multi = false;
			bool done = false;
			pfc::string8 bad_data_message;
			for(t_size n=0;n<count && !done;n++) {
				try {
					p_list[n]->open(p_instance,p_filehint,p_path,p_abort);
					done = true;
				} catch(exception_io_unsupported_format) {
					//do nothing, skip over
				} catch(exception_io_data const & e) {
					if (!got_bad_data) bad_data_message = e.what();
					else got_bad_data_multi = true;
					got_bad_data = true;
				}
			}
			if (!done) {
				if (got_bad_data_multi) throw exception_io_data();
				else if (got_bad_data) throw exception_io_data(bad_data_message);
				else throw exception_io_unsupported_format();
			}
		}
	}

	template<typename t_service> bool needs_write_access() {return false;}
	template<> bool needs_write_access<input_info_writer>() {return true;}

	template<typename t_service> void g_open_t(service_ptr_t<t_service> & p_instance,service_ptr_t<file> const & p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) {
		service_ptr_t<file> l_file = p_filehint;
		if (l_file.is_empty()) {
			service_ptr_t<filesystem> fs;
			if (filesystem::g_get_interface(fs,p_path)) {
				if (fs->supports_content_types()) {
					fs->open(l_file,p_path,needs_write_access<t_service>() ? filesystem::open_mode_write_existing : filesystem::open_mode_read,p_abort);
				}
			}
		}

		if (l_file.is_valid()) {
			pfc::string8 content_type;
			if (l_file->get_content_type(content_type)) {
				pfc::list_hybrid_t<service_ptr_t<input_entry>,4> list;
				if (g_find_inputs_by_content_type(list,content_type,p_from_redirect)) {
					g_open_from_list(p_instance,list,l_file,p_path,p_abort);
					return;
				}
			}
		}

		{
			pfc::list_hybrid_t<service_ptr_t<input_entry>,4> list;
			if (g_find_inputs_by_path(list,p_path,p_from_redirect)) {
				g_open_from_list(p_instance,list,l_file,p_path,p_abort);
				return;
			}
		}

		throw exception_io_unsupported_format();
	}
};

void input_entry::g_open_for_decoding(service_ptr_t<input_decoder> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) {
	TRACK_CALL_TEXT("input_entry::g_open_for_decoding");
#if 1
	g_open_t(p_instance,p_filehint,p_path,p_abort,p_from_redirect);
#else
	service_ptr_t<file> filehint = p_filehint;
	service_ptr_t<input_entry> entry;

	prepare_for_open(entry,filehint,p_path,filesystem::open_mode_read,p_abort,p_from_redirect);

	entry->open_for_decoding(p_instance,filehint,p_path,p_abort);
#endif

}

void input_entry::g_open_for_info_read(service_ptr_t<input_info_reader> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) {
	TRACK_CALL_TEXT("input_entry::g_open_for_info_read");
#if 1
	g_open_t(p_instance,p_filehint,p_path,p_abort,p_from_redirect);
#else
	service_ptr_t<file> filehint = p_filehint;
	service_ptr_t<input_entry> entry;

	prepare_for_open(entry,filehint,p_path,filesystem::open_mode_read,p_abort,p_from_redirect);

	entry->open_for_info_read(p_instance,filehint,p_path,p_abort);
#endif
}

void input_entry::g_open_for_info_write(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) {
	TRACK_CALL_TEXT("input_entry::g_open_for_info_write");
#if 1
	g_open_t(p_instance,p_filehint,p_path,p_abort,p_from_redirect);
#else
	service_ptr_t<file> filehint = p_filehint;
	service_ptr_t<input_entry> entry;

	prepare_for_open(entry,filehint,p_path,filesystem::open_mode_write_existing,p_abort,p_from_redirect);

	entry->open_for_info_write(p_instance,filehint,p_path,p_abort);
#endif
}

void input_entry::g_open_for_info_write_timeout(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,double p_timeout,bool p_from_redirect) {
	pfc::lores_timer timer;
	timer.start();
	for(;;) {
		try {
			g_open_for_info_write(p_instance,p_filehint,p_path,p_abort,p_from_redirect);
			break;
		} catch(exception_io_sharing_violation) {
			if (timer.query() > p_timeout) throw;
			p_abort.sleep(0.01);
		}
	}
}

bool input_entry::g_is_supported_path(const char * p_path)
{
	service_ptr_t<input_entry> ptr;
	service_enum_t<input_entry> e;
	pfc::string_extension ext(p_path);
	while(e.next(ptr))
	{
		if (ptr->is_our_path(p_path,ext)) return true;
	}
	return false;
}



void input_open_file_helper(service_ptr_t<file> & p_file,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort)
{
	if (p_file.is_empty()) {
		switch(p_reason) {
		default:
			uBugCheck();
		case input_open_info_read:
		case input_open_decode:
			filesystem::g_open(p_file,p_path,filesystem::open_mode_read,p_abort);
			break;
		case input_open_info_write:
			filesystem::g_open(p_file,p_path,filesystem::open_mode_write_existing,p_abort);
			break;
		}
	} else {
		p_file->reopen(p_abort);
	}
}