/*
    libmaus2
    Copyright (C) 2009-2015 German Tischler
    Copyright (C) 2011-2015 Genome Research Limited

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#if ! defined(LIBMAUS2_AIO_INPUTSTREAMFACTORYCONTAINER_HPP)
#define LIBMAUS2_AIO_INPUTSTREAMFACTORYCONTAINER_HPP

#include <libmaus2/aio/InputStreamFactory.hpp>
#include <libmaus2/aio/PosixFdInputStreamFactory.hpp>
#include <libmaus2/aio/MemoryInputStreamFactory.hpp>
#include <libmaus2/network/UrlInputStreamFactory.hpp>
#include <libmaus2/aio/IFStreamInputStreamFactory.hpp>
#include <libmaus2/aio/SecrecyInputStreamFactory.hpp>
#include <libmaus2/aio/URLObject.hpp>
#include <cctype>

namespace libmaus2
{
	namespace aio
	{
		struct InputStreamFactoryContainer
		{
			private:
			static std::map<std::string,libmaus2::aio::InputStreamFactory::shared_ptr_type> factories;

			static std::map<std::string,libmaus2::aio::InputStreamFactory::shared_ptr_type> setupFactories()
			{
				std::map<std::string,libmaus2::aio::InputStreamFactory::shared_ptr_type> tfactories;

				#if defined(LIBMAUS2_AIO_POSIXFDINPUTSTREAM_SUPPORTED)
				libmaus2::aio::PosixFdInputStreamFactory::shared_ptr_type tfilefact(new libmaus2::aio::PosixFdInputStreamFactory);
				tfactories["file"] = tfilefact;
				#endif

				libmaus2::aio::IFStreamInputStreamFactory::shared_ptr_type tifstreamfact(new libmaus2::aio::IFStreamInputStreamFactory);
				tfactories["stdcxx"] = tifstreamfact;
				#if ! defined(LIBMAUS2_AIO_POSIXFDINPUTSTREAM_SUPPORTED)
				tfactories["file"] = tifstreamfact;
				#endif

				libmaus2::aio::MemoryInputStreamFactory::shared_ptr_type tmemfact(new libmaus2::aio::MemoryInputStreamFactory);
				tfactories["mem"] = tmemfact;

				libmaus2::network::UrlInputStreamFactory::shared_ptr_type turlfact(new libmaus2::network::UrlInputStreamFactory);
				tfactories["ftp"] = turlfact;
				tfactories["http"] = turlfact;
				tfactories["https"] = turlfact;

				#if defined(LIBMAUS2_HAVE_LIBSECRECY)
				libmaus2::aio::SecrecyInputStreamFactory::shared_ptr_type tsecfact(new libmaus2::aio::SecrecyInputStreamFactory);
				tfactories["libsecrecy"] = tsecfact;
				#endif

				return tfactories;
			}

			static libmaus2::aio::InputStreamFactory::shared_ptr_type haveFactoryForProtocol(std::string const & url)
			{
				libmaus2::aio::URLObject const U(url);
				libmaus2::aio::InputStreamFactory::shared_ptr_type ptr;

				if ( U.protocol.size() )
				{
					auto const it = factories.find(U.protocol);

					if ( it != factories.end() )
						ptr = it->second;
				}

				return ptr;
			}

			static libmaus2::aio::InputStreamFactory::shared_ptr_type getFactory(std::string const & url)
			{
				libmaus2::aio::InputStreamFactory::shared_ptr_type ptr =
					haveFactoryForProtocol(url);

				return ptr ? ptr : factories.find("file")->second;
			}

			public:
			static libmaus2::aio::InputStream::unique_ptr_type constructUnique(std::string const & url)
			{
				libmaus2::aio::InputStreamFactory::shared_ptr_type factory = getFactory(url);

				if ( haveFactoryForProtocol(url) )
				{
					libmaus2::aio::URLObject U(url);
					std::string const protocol = U.protocol;

					if ( protocol == "ftp" || protocol == "http" || protocol == "https" )
					{
						libmaus2::aio::InputStream::unique_ptr_type tptr(factory->constructUnique(url));
						return tptr;
					}
					else
					{
						libmaus2::aio::InputStream::unique_ptr_type tptr(
							factory->constructUnique(U.path,U.parameters)
						);
						return tptr;
					}
				}
				else
				{
					libmaus2::aio::InputStream::unique_ptr_type tptr(factory->constructUnique(url));
					return tptr;
				}
			}

			static libmaus2::aio::InputStream::shared_ptr_type constructShared(std::string const & url)
			{
				libmaus2::aio::InputStreamFactory::shared_ptr_type factory = getFactory(url);

				if ( haveFactoryForProtocol(url) )
				{
					libmaus2::aio::URLObject U(url);
					std::string const protocol = U.protocol;

					if ( protocol == "ftp" || protocol == "http" || protocol == "https" )
					{
						libmaus2::aio::InputStream::shared_ptr_type tptr(factory->constructShared(url));
						return tptr;
					}
					else
					{
						libmaus2::aio::InputStream::shared_ptr_type tptr(factory->constructShared(U.path,U.parameters));
						return tptr;
					}
				}
				else
				{
					libmaus2::aio::InputStream::shared_ptr_type tptr(factory->constructShared(url));
					return tptr;
				}
			}

			static bool tryOpen(std::string const & url)
			{
				libmaus2::aio::InputStreamFactory::shared_ptr_type factory = getFactory(url);

				if ( haveFactoryForProtocol(url) )
				{
					libmaus2::aio::URLObject U(url);
					std::string const protocol = U.protocol;

					if ( protocol == "ftp" || protocol == "http" || protocol == "https" )
					{
						return factory->tryOpen(url);
					}
					else
					{
						return factory->tryOpen(U.path,U.parameters);
					}
				}
				else
				{
					return factory->tryOpen(url);
				}
			}

			static void addHandler(std::string const & protocol, libmaus2::aio::InputStreamFactory::shared_ptr_type factory)
			{
				factories[protocol] = factory;
			}

			static void removeHandler(std::string const & protocol)
			{
				std::map<std::string,libmaus2::aio::InputStreamFactory::shared_ptr_type>::iterator it = factories.find(protocol);
				if ( it != factories.end() )
					factories.erase(it);
			}

			static bool isRegularFileURL(std::string const & url)
			{
				assert ( factories.find("file") != factories.end() );
				libmaus2::aio::InputStreamFactory::shared_ptr_type ptr = getFactory(url);
				return ptr.get() == factories.find("file")->second.get();
			}
		};
	}
}
#endif
