/*
 * Copyright (C) 2008-2013 Sakari Bergen <sakari.bergen@beatwaves.net>
 * Copyright (C) 2008-2017 Paul Davis <paul@linuxaudiosystems.com>
 * Copyright (C) 2009-2010 Carl Hetherington <carl@carlh.net>
 * Copyright (C) 2009-2011 David Robillard <d@drobilla.net>
 * Copyright (C) 2012-2013 Tim Mayberry <mojofunk@gmail.com>
 * Copyright (C) 2016-2018 Robin Gareus <robin@gareus.org>
 *
 * 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 2 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#pragma once

#include <list>
#include <map>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>

#include "pbd/file_utils.h"
#include "pbd/uuid.h"
#include "pbd/xml++.h"

#include "ardour/export_handler.h"
#include "ardour/filesystem_paths.h"
#include "ardour/libardour_visibility.h"
#include "ardour/location.h"
#include "ardour/types.h"

namespace ARDOUR
{
class ExportHandler;
class Location;
class Session;

/// Manages (de)serialization of export profiles and related classes
class LIBARDOUR_API ExportProfileManager
{
public:
	enum ExportType {
		RegularExport,
		RangeExport,
		SelectionExport,
		RegionExport,
		StemExport
	};

	ExportProfileManager (Session& s, ExportType type);
	~ExportProfileManager ();

	void load_profile ();
	void prepare_for_export ();

	ExportType type () const { return _type; }

	typedef std::list<ExportPresetPtr> PresetList;

	PresetList const& get_presets () { return preset_list; }
	ExportPresetPtr preset () const { return current_preset; }

	bool            load_preset (ExportPresetPtr preset);
	ExportPresetPtr new_preset (std::string const& name);
	ExportPresetPtr save_preset (std::string const& name);
	void            remove_preset ();

private:
	typedef std::shared_ptr<ExportHandler> HandlerPtr;

	typedef std::pair<PBD::UUID, std::string> FilePair;
	typedef std::map<PBD::UUID, std::string>  FileMap;

	ExportType  _type;
	std::string xml_node_name;
	HandlerPtr  handler;
	Session&    session;

	std::string preset_filename (std::string const& preset_name);
	void        load_presets ();
	void        load_preset_from_disk (std::string const& path);

	bool set_state (XMLNode const& root);
	bool set_global_state (XMLNode const& root);
	bool set_local_state (XMLNode const& root);

	void serialize_profile (XMLNode& root);
	void serialize_global_profile (XMLNode& root);
	void serialize_local_profile (XMLNode& root);

	PresetList      preset_list;
	ExportPresetPtr current_preset;
	FileMap         preset_file_map;

	std::vector<std::string> find_file (std::string const& pattern);

	std::string     export_config_dir;
	PBD::Searchpath search_path;

	/* Timespans */
public:
	typedef std::list<ExportTimespanPtr>    TimespanList;
	typedef std::shared_ptr<TimespanList> TimespanListPtr;
	typedef std::list<Location*>            LocationList;

	enum TimeFormat {
		Timecode,
		BBT,
		MinSec,
		Samples,
	};

	struct TimespanState {
		TimespanListPtr timespans;
		TimeFormat      time_format;

		std::shared_ptr<Location>     selection_range;
		std::shared_ptr<LocationList> ranges;

		TimespanState (std::shared_ptr<Location>     selection_range,
		               std::shared_ptr<LocationList> ranges)
		    : timespans (new TimespanList ())
		    , time_format (Timecode)
		    , selection_range (selection_range)
		    , ranges (ranges)
		{
		}
	};

	typedef std::shared_ptr<TimespanState> TimespanStatePtr;
	typedef std::list<TimespanStatePtr>      TimespanStateList;

	void        set_selection_range (samplepos_t start = 0, samplepos_t end = 0);
	std::string set_single_range (samplepos_t start, samplepos_t end, std::string name);

	TimespanStateList const& get_timespans () { return check_list (timespans); }

private:
	TimespanStateList timespans;

	bool init_timespans (XMLNodeList nodes);

	TimespanStatePtr deserialize_timespan (XMLNode& root);
	XMLNode&         serialize_timespan (TimespanStatePtr state);

	/* Locations */
	void update_ranges ();

	std::shared_ptr<Location>     selection_range;
	std::shared_ptr<LocationList> ranges;
	std::shared_ptr<Location>     single_range;

	bool single_range_mode;

	/* Channel Configs */
public:
	struct ChannelConfigState {
		ExportChannelConfigPtr config;

		ChannelConfigState (ExportChannelConfigPtr ptr)
		    : config (ptr)
		{
		}
	};

	typedef std::shared_ptr<ChannelConfigState> ChannelConfigStatePtr;
	typedef std::list<ChannelConfigStatePtr>      ChannelConfigStateList;

	ChannelConfigStateList const& get_channel_configs () { return check_list (channel_configs); }

	void clear_channel_configs () { channel_configs.clear (); }

	ChannelConfigStatePtr add_channel_config ();

private:
	ChannelConfigStateList channel_configs;

	bool init_channel_configs (XMLNodeList nodes);

	/* Formats */
public:
	typedef std::list<ExportFormatSpecPtr> FormatList;

	struct FormatState {
		std::shared_ptr<FormatList const> list;
		ExportFormatSpecPtr                 format;

		FormatState (std::shared_ptr<FormatList const> list, ExportFormatSpecPtr format)
		    : list (list)
		    , format (format)
		{
		}
	};

	typedef std::shared_ptr<FormatState> FormatStatePtr;
	typedef std::list<FormatStatePtr>      FormatStateList;

	FormatStateList const& get_formats () { return check_list (formats); }

	FormatStatePtr duplicate_format_state (FormatStatePtr state);
	void           remove_format_state (FormatStatePtr state);

	std::string save_format_to_disk (ExportFormatSpecPtr format);
	void        remove_format_profile (ExportFormatSpecPtr format);
	void        revert_format_profile (ExportFormatSpecPtr format);

	ExportFormatSpecPtr get_new_format (ExportFormatSpecPtr original);

	PBD::Signal<void()> FormatListChanged;

private:
	FormatStateList formats;

	bool           init_formats (XMLNodeList nodes);
	FormatStatePtr deserialize_format (XMLNode& root);
	XMLNode&       serialize_format (FormatStatePtr state);

	void load_formats ();

	ExportFormatSpecPtr load_format (XMLNode& node);
	void                load_format_from_disk (std::string const& path);

	std::shared_ptr<FormatList> format_list;
	FileMap                       format_file_map;

	/* Filenames */
public:
	struct FilenameState {
		ExportFilenamePtr filename;

		FilenameState (ExportFilenamePtr ptr)
		    : filename (ptr)
		{
		}
	};

	typedef std::shared_ptr<FilenameState> FilenameStatePtr;
	typedef std::list<FilenameStatePtr>      FilenameStateList;

	FilenameStateList const& get_filenames () { return check_list (filenames); }

	FilenameStatePtr duplicate_filename_state (FilenameStatePtr state);
	void             remove_filename_state (FilenameStatePtr state);

	std::string get_sample_filename_for_format (ExportFilenamePtr filename, ExportFormatSpecPtr format);

private:
	FilenameStateList filenames;

	bool              init_filenames (XMLNodeList nodes);
	ExportFilenamePtr load_filename (XMLNode& node);

	/* Warnings */
public:
	struct Warnings {
		std::list<std::string> errors;
		std::list<std::string> warnings;
		std::list<std::string> conflicting_filenames;
	};

	std::shared_ptr<Warnings> get_warnings ();

private:
	void check_config (std::shared_ptr<Warnings> warnings,
	                   TimespanStatePtr            timespan_state,
	                   ExportChannelConfigPtr      channel_config,
	                   FormatStatePtr              format_state,
	                   FilenameStatePtr            filename_state);

	bool check_format (ExportFormatSpecPtr format, uint32_t channels);
	bool check_sndfile_format (ExportFormatSpecPtr format, unsigned int channels);

	/* Utilities */

	void build_filenames (std::list<std::string>& result, ExportFilenamePtr filename,
	                      TimespanListPtr timespans, ExportChannelConfigPtr channel_config,
	                      ExportFormatSpecPtr format);

	/* Element state lists should never be empty, this is used to check them */
	template <typename T>
	std::list<T> const&
	check_list (std::list<T> const& list)
	{
		if (list.empty ()) {
			throw std::runtime_error ("Programming error: Uninitialized list in ExportProfileManager");
		}
		return list;
	}
};

} // namespace ARDOUR

