/**
 *	A Tabbar implementation
 *	Nana C++ Library(http://www.nanapro.org)
 *	Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com)
 *
 *	Distributed under the Boost Software License, Version 1.0.
 *	(See accompanying file LICENSE_1_0.txt or copy at
 *	http://www.boost.org/LICENSE_1_0.txt)
 *
 *   @file:  nana/gui/widgets/tabbar.hpp
 *	 @brief A tabbar contains tab items and toolbox for scrolling, closing, selecting items.
 *
 */
#ifndef NANA_GUI_WIDGET_TABBAR_HPP
#define NANA_GUI_WIDGET_TABBAR_HPP
#include <nana/push_ignore_diagnostic>

#include "widget.hpp"
#include <nana/pat/cloneable.hpp>
#include <nana/any.hpp>

namespace nana
{
	template<typename T> class tabbar;

	template<typename T>
	struct arg_tabbar
		: public event_arg
	{
		tabbar<T> & widget;
		T & value;
		std::size_t item_pos;						///< position of the item

		arg_tabbar(tabbar<T>& wdg, T& v, std::size_t p)
			: widget(wdg), value{ v }, item_pos(p)
		{}
	};

	template<typename T>
	struct arg_tabbar_click : public arg_tabbar<T>
	{
		arg_tabbar_click(tabbar<T>& wdg, T& v, std::size_t p)
			: arg_tabbar<T>({wdg, v, p})
		{}

		bool left_button = false;					///< true if mouse left button is pressed
		bool mid_button = false;					///< true if mouse middle button is pressed
		bool right_button = false;					///< true if mouse right button is pressed
	};

	template<typename T>
	struct arg_tabbar_mouse
		: public arg_mouse
	{
		arg_tabbar_mouse(const arg_mouse& arg, tabbar<T>& wdg, T& v, std::size_t p)
			: arg_mouse{ arg }, widget(wdg), value{ v }, item_pos(p)
		{}

		tabbar<T> & widget;
		T & value;
		std::size_t item_pos;						///< position of the item
	};

	template<typename T>
	struct arg_tabbar_adding
		: public event_arg
	{
		arg_tabbar_adding(tabbar<T>& wdg, std::size_t p)
			: widget(wdg), where(p)
		{}

		tabbar<T> & widget;
		mutable bool add = true;					///< determines whether to add the item
		std::size_t where;							///< position where to add the item
	};

	template<typename T>
	struct arg_tabbar_removed : public arg_tabbar<T>
	{
		arg_tabbar_removed(tabbar<T>& wdg, T& v, std::size_t p)
			: arg_tabbar<T>({wdg, v, p})
		{}

		mutable bool remove = true;					///< determines whether to remove the item
		mutable bool close_attach_window = true;	///< determines whether to close the attached window. It is ignored if remove is false
	};

	namespace drawerbase
	{
		namespace tabbar
		{
			template<typename T>
			struct tabbar_events
				: public general_events
			{
				using value_type = T;

				basic_event<arg_tabbar_adding<value_type>> adding;
				basic_event<arg_tabbar<value_type>> added;
				basic_event<arg_tabbar_mouse<value_type>> tab_click;
				basic_event<arg_tabbar<value_type>> activated;
				basic_event<arg_tabbar_removed<value_type>> removed;
			};

			class event_agent_interface
			{
			public:
				virtual ~event_agent_interface() = default;
				virtual bool adding(std::size_t) = 0;
				virtual void added(std::size_t) = 0;
				virtual bool click(const arg_mouse&, std::size_t) = 0;
				virtual void activated(std::size_t) = 0;
				virtual bool removed(std::size_t, bool & close_attached) = 0;
			};

			class item_renderer
			{
			public:
				typedef item_renderer item_renderer_type;
				typedef ::nana::paint::graphics & graph_reference;
				enum state_t{disable, normal, highlight, press};

				struct item_t
				{
					::nana::rectangle r;
					::nana::color	bgcolor;
					::nana::color	fgcolor;
				};

				virtual ~item_renderer() = default;
				virtual void background(graph_reference, const nana::rectangle& r, const ::nana::color& bgcolor) = 0;
				virtual void item(graph_reference, const item_t&, bool active, state_t) = 0;
				virtual void close_fly(graph_reference, const nana::rectangle&, bool active, state_t) = 0;

				virtual void add(graph_reference, const nana::rectangle&, state_t) = 0;
				virtual void close(graph_reference, const nana::rectangle&, state_t) = 0;
				virtual void back(graph_reference, const nana::rectangle&, state_t) = 0;
				virtual void next(graph_reference, const nana::rectangle&, state_t) = 0;
				virtual void list(graph_reference, const nana::rectangle&, state_t) = 0;
			};

			template<typename T, typename DrawerTrigger>
			class event_agent
				: public event_agent_interface
			{
			public:
				using arg_tabbar = ::nana::arg_tabbar<T>;

				event_agent(::nana::tabbar<T>& tb, DrawerTrigger & dtr)
					: tabbar_(tb), drawer_trigger_(dtr)
				{}

				bool adding(std::size_t pos) override
				{
					::nana::arg_tabbar_adding<T> arg_ta(tabbar_, pos);
					tabbar_.events().adding.emit(arg_ta, tabbar_);
					return arg_ta.add;
				}

				void added(std::size_t pos) override
				{
					if(pos != npos)
					{
						drawer_trigger_.at_no_bound_check(pos) = T();
						tabbar_.events().added.emit(arg_tabbar({ tabbar_, tabbar_[pos], pos }), tabbar_);
					}
				}

				bool click(const arg_mouse& arg, std::size_t pos) override
				{
					::nana::arg_tabbar_mouse<T> arg_tm(arg, tabbar_, tabbar_[pos], pos);
					tabbar_.events().tab_click.emit(arg_tm, tabbar_);
					return arg_tm.propagation_stopped();
				}

				void activated(std::size_t pos) override
				{
					if(pos != npos)
						tabbar_.events().activated.emit(arg_tabbar({ tabbar_, tabbar_[pos], pos}), tabbar_);
				}

				bool removed(std::size_t pos, bool & close_attach) override
				{
					if(pos != npos)
					{
						::nana::arg_tabbar_removed<T> arg(tabbar_, tabbar_[pos], pos);
						tabbar_.events().removed.emit(arg, tabbar_);
						close_attach = arg.close_attach_window;
						return arg.remove;
					}
					close_attach = true;
					return true;
				}
			private:
				::nana::tabbar<T> & tabbar_;
				DrawerTrigger& drawer_trigger_;
			};

			class layouter;

			class trigger
				: public drawer_trigger
			{
			public:
				using native_string_type = ::nana::detail::native_string_type;

				enum class kits
				{
					add,	///< The type identifies the add button of the tabbar's toolbox.
					scroll,	///< The type identifies the scroll button of the tabbar's toolbox
					list,	///< The type identifies the list button of the tabbar's toolbox
					close	///< The type identifies the close button of the tabbar's toolbox
				};

				trigger();
				~trigger();
				void activate(std::size_t);
				std::size_t activated() const;
				nana::any& at(std::size_t) const;
				nana::any& at_no_bound_check(std::size_t) const;
				const pat::cloneable<item_renderer> & ext_renderer() const;
				void ext_renderer(const pat::cloneable<item_renderer>&);
				void set_event_agent(event_agent_interface*);
				void insert(std::size_t, native_string_type&&, nana::any&&);
				std::size_t length() const;
				bool close_fly(bool);
				window attach(std::size_t, window, bool drop_other);
				void erase(std::size_t);
				void tab_color(std::size_t, bool is_bgcolor, const ::nana::color&);
				void tab_image(size_t, const nana::paint::image&);
				void text(std::size_t, const native_string_type&);
				native_string_type text(std::size_t) const;
				bool toolbox(kits, bool);
			private:
				void attached(widget_reference, graph_reference)	override;
				void detached()	override;
				void refresh(graph_reference)	override;
				void mouse_down(graph_reference, const arg_mouse&)	override;
				void mouse_up(graph_reference, const arg_mouse&)	override;
				void mouse_move(graph_reference, const arg_mouse&)	override;
				void mouse_leave(graph_reference, const arg_mouse&)	override;
			private:
				layouter * layouter_;
			};
		}
	}//end namespace drawerbase
    /// Analogous to dividers in a notebook or the labels in a file cabinet
	template<typename Type>
	class tabbar
		: public widget_object<category::widget_tag, drawerbase::tabbar::trigger, drawerbase::tabbar::tabbar_events<Type>>
	{
		typedef drawerbase::tabbar::trigger drawer_trigger_t;
	public:
		using value_type = Type;            ///< The type of element data which is stored in the tabbar.
		using item_renderer = drawerbase::tabbar::item_renderer; ///< A user-defined item renderer should be derived from this interface.
		using kits = drawer_trigger_t::kits;

		tabbar()
		{
			evt_agent_.reset(new drawerbase::tabbar::event_agent<value_type, drawer_trigger_t>(*this, this->get_drawer_trigger()));
			this->get_drawer_trigger().set_event_agent(evt_agent_.get());
		}

		tabbar(window wd, bool visible)
			: tabbar()
		{
			this->create(wd, rectangle(), visible);
		}

		tabbar(window wd, const rectangle& r = rectangle(), bool visible = true)
			: tabbar()
		{
			this->create(wd, r, visible);
		}

		~tabbar()
		{
			this->get_drawer_trigger().set_event_agent(nullptr);
		}

		value_type & operator[](std::size_t pos) const
		{
			return any_cast<value_type&>(this->get_drawer_trigger().at_no_bound_check(pos));
		}

		void activated(std::size_t pos)                  /// Activates a tab specified by pos.
		{
			this->get_drawer_trigger().activate(pos);
		}

		std::size_t activated() const
		{
			return this->get_drawer_trigger().activated();
		}

		value_type const & at(std::size_t pos) const        /// Returns pos'th element
		{
			return any_cast<value_type&>(this->get_drawer_trigger().at(pos));
		}

		void close_fly(bool fly)                    /// Draw or not a close button in each tab.
		{
			if (this->get_drawer_trigger().close_fly(fly))
				API::refresh_window(this->handle());
		}

		const pat::cloneable<item_renderer>& renderer() const
		{
			return this->get_drawer_trigger().ext_renderer();
		}

		void renderer(const pat::cloneable<item_renderer>& ir)
		{
			this->get_drawer_trigger().ext_renderer(ir);
		}

		std::size_t length() const                 /// Returns the number of items.
		{
			return this->get_drawer_trigger().length();
		}

		/// Append a new tab
		tabbar& append(std::string text, window attach_wd, value_type value = {})  // 2x text convertion. maybe better to duplicate code?
		{
			return this->append( static_cast<std::wstring&&>(nana::charset(std::move(text), nana::unicode::utf8)), attach_wd, std::move(value));
		}

		tabbar& append(std::wstring text, window attach_wd, value_type value = {})
		{
			if (attach_wd && API::empty_window(attach_wd))
				throw std::invalid_argument("Appending a tab to a tabbar - error: tabbar.attach: invalid window handle");

			this->get_drawer_trigger().insert(::nana::npos, to_nstring(std::move(text)), std::move(value));
			if (attach_wd)
				this->attach(this->get_drawer_trigger().length() - 1, attach_wd);
			
			API::update_window(*this);
			return *this;
		}

		void push_back(std::string text)  /// Append a new item.
		{
			this->get_drawer_trigger().insert(::nana::npos, to_nstring(std::move(text)), value_type());
			API::update_window(*this);
		}

		void insert(std::size_t pos, std::string text, value_type value = {})
		{
			if (pos > length())
				throw std::out_of_range("tabbar::insert invalid position");

			this->get_drawer_trigger().insert(pos, to_nstring(std::move(text)), std::move(value));
			API::update_window(*this);
		}

		void insert(std::size_t pos, std::wstring text, value_type value = {})
		{
			if (pos > length())
				throw std::out_of_range("tabbar::insert invalid position");

			this->get_drawer_trigger().insert(pos, to_nstring(std::move(text)), std::move(value));
			API::update_window(*this);
		}

		/// Attach a window to a specified tab. When the tab is activated, tabbar shows the attached window. 
		/**
		 * @param pos The position of tab to set the attached window.
		 * @param attach_wd A handle to the window to be set.
		 * @param drop_other Drop the attached windows of other tabs whose attach windows are equal to the parameter attach_wd. If drop_other is true, the other tabs attached windows equal to attach_wd will be dropped.
		 * @return A handle to the last attached window of specified tab.
		 */
		window attach(std::size_t pos, window attach_wd, bool drop_other = true)
		{
			if (attach_wd && API::empty_window(attach_wd))
				throw std::invalid_argument("tabbar.attach: invalid window handle");

			return this->get_drawer_trigger().attach(pos, attach_wd, drop_other);
		}

		void erase(std::size_t pos)
		{
			this->get_drawer_trigger().erase(pos);
		}

		void tab_bgcolor(std::size_t pos, const ::nana::color& clr)
		{
			this->get_drawer_trigger().tab_color(pos, true, clr);
		}

		void tab_fgcolor(std::size_t pos, const ::nana::color& clr)
		{
			this->get_drawer_trigger().tab_color(pos, false, clr);
		}

		void tab_image(std::size_t pos, const nana::paint::image& img)
		{
			this->get_drawer_trigger().tab_image(pos, img);
		}

        /// Sets buttons of the tabbar's toolbox, refer to notes for more details.
		void toolbox(kits kit, bool enable)
		{
			if (this->get_drawer_trigger().toolbox(kit, enable))
				API::refresh_window(this->handle());
		}

		void text(std::size_t pos, const std::string& str) /// Sets the title of the specified item, If pos is invalid, the method throws an std::out_of_range object.
		{
			this->get_drawer_trigger().text(pos, to_nstring(str));
		}

		std::string text(std::size_t pos) const /// Returns a title of a specified item, If pos is invalid, the method throws a std::out_of_range object.
		{
			return to_utf8(this->get_drawer_trigger().text(pos));
		}
	private:
		std::unique_ptr<drawerbase::tabbar::event_agent<value_type, drawerbase::tabbar::trigger> > evt_agent_;
	};
}//end namespace nana


namespace nana
{	
		namespace drawerbase
		{
			namespace tabbar_lite
			{
				class model;

				struct events
					: public general_events
				{
					basic_event<event_arg> selected;
				};

				class driver
					: public drawer_trigger
				{
				public:
					driver();
					~driver();

					model* get_model() const noexcept;
				private:
					//Overrides drawer_trigger's method
					void attached(widget_reference, graph_reference)	override;
					void refresh(graph_reference) override;
					void mouse_move(graph_reference, const arg_mouse&) override;
					void mouse_leave(graph_reference, const arg_mouse&) override;
					void mouse_down(graph_reference, const arg_mouse&) override;
				private:
					model* const model_;
				};
			}
		}//end namespace drawerbase

	class tabbar_lite
		: public widget_object<category::widget_tag, drawerbase::tabbar_lite::driver, drawerbase::tabbar_lite::events>
	{
	public:
		tabbar_lite() = default;
		tabbar_lite(window, bool visible = true, const::nana::rectangle& = {});

	public: //capacity
		std::size_t length() const;

	public: //modifiers
		void attach(std::size_t pos, window);
		window attach(std::size_t pos) const;

		void push_back(std::string text, ::nana::any par = {});
		void push_front(std::string text, ::nana::any par = {});

		std::size_t selected() const;
		void erase(std::size_t pos, bool close_attached = true);
	};
}
#include <nana/pop_ignore_diagnostic>

#endif
