/*
 * Copyright (C) 2016-2017 Paul Davis <paul@linuxaudiosystems.com>
 * Copyright (C) 2016-2019 Robin Gareus <robin@gareus.org>
 * Copyright (C) 2018 Len Ovens <len@ovenwerks.net>
 *
 * 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 <iostream>
#include <string>

#include <atomic>
#include <cstdint>

#include "pbd/signals.h"
#include "pbd/stateful.h"
#include "pbd/properties.h"

#include "ardour/libardour_visibility.h"

class XMLNode;

namespace ARDOUR {

namespace Properties {
	LIBARDOUR_API extern PBD::PropertyDescriptor<uint32_t> order;
	LIBARDOUR_API extern PBD::PropertyDescriptor<uint32_t> color;
	LIBARDOUR_API extern PBD::PropertyDescriptor<bool> selected;
	LIBARDOUR_API extern PBD::PropertyDescriptor<bool> trigger_track;
	/* we use this; declared in region.cc */
	LIBARDOUR_API extern PBD::PropertyDescriptor<bool> hidden;
}

class LIBARDOUR_API PresentationInfo : public PBD::Stateful
{
  public:

	/* a PresentationInfo object exists to share information between
	 * different user interfaces (e.g. GUI and a Mackie Control surface)
	 * about:
	 *
	 *     - ordering
	 *     - selection status
	 *     - visibility
	 *     - object identity
	 *
	 * ORDERING
	 *
	 * One UI takes control of ordering by setting the "order" value for
	 * the PresentationInfo component of every Stripable object. In Ardour,
	 * this is done by the GUI (mostly because it is very hard for the user
	 * to re-order things on a control surface).
	 *
	 * Ordering is a complex beast, however. Different user interfaces may
	 * display things in different ways. For example, the GUI of Ardour
	 * allows the user to mix busses in between tracks. A control surface
	 * may do the same, but may also allow the user to press a button that
	 * makes it show only busses, or only MIDI tracks. At that point, the
	 * ordering on the surface differs from the ordering in the GUI.
	 *
	 * There are several pathways for the order being set:
	 *
	 *   - object created during session loading from XML
	 *   - numeric order will be set during ::set_state(), based on
	 *   - type will be set during ctor call
	 *
	 *   - object created in response to user request
	 *   - numeric order will be set by Session, before adding to container.
	 *   - type set during ctor call
	 *
	 *
	 * OBJECT IDENTITY
	 *
	 * Control surfaces/protocols often need to be able to get a handle on
	 * an object identified only abstractly, such as the "5th audio track"
	 * or "the master out". A PresentationInfo object uniquely identifies
	 * all objects in this way through the combination of its _order member
	 * and part of its _flags member. The _flags member identifies the type
	 * of object, as well as selection/hidden status. The type may never
	 * change after construction (not strictly the constructor itself, but
	 * a more generalized notion of construction, as in "ready to use").
	 *
	 * VISIBILITY
	 *
	 * When an object is hidden, its _flags member will have the Hidden
	 * bit set.
	 *
	 *
	 */

	enum Flag {
		/* Type information */
		AudioTrack = 0x1,
		MidiTrack = 0x2,
		AudioBus = 0x4,
		MidiBus = 0x8,
		VCA = 0x10,
		MasterOut = 0x20,
		MonitorOut = 0x40,
		Auditioner = 0x80,
		/* These are for sharing Stripable states between the GUI and other
		 * user interfaces/control surfaces
		 */
		Hidden = 0x100,
		/* single bit indicates that the group order is set */
		OrderSet = 0x400,

		/* Mixbus specific */
		MixbusEditorHidden = 0x800,
		Mixbus = 0x1000,

		/* bus type for monitor mixes */
		FoldbackBus = 0x2000,

		/* has TriggerBox, show on TriggerUI page */
		TriggerTrack = 0x4000,

#ifndef VBM
		/* bus is the surround master */
		SurroundMaster = 0x8000,
#else
		SurroundMaster = 0, // for compatibility
#endif

		/* VBM */
		Program     = 0x4000,  // deprecated (conflicts with TriggerTrack)
		MixMinus    = 0x8000,  // deprecated (conflicts with SurroundMaster)
		VBMProgram  = 0x10000,
		VBMMixMinus = 0x20000,
#ifdef VBM
		VBMAny      = 0x3c000,
#else
		VBMAny      = 0x30000,
#endif

		/* special mask to detect out "state" bits */
		StatusMask = (Hidden | MixbusEditorHidden | TriggerTrack),

		/* dedicated [output] busses */
		MainBus = (MasterOut|MonitorOut|FoldbackBus|SurroundMaster),

		/* These can exist only once and require special attention to be removed */
		Singleton = (MasterOut|MonitorOut|SurroundMaster),

		/* special mask to detect select type bits */
		TypeMask = (AudioBus|AudioTrack|MidiTrack|MidiBus|Mixbus|VCA|MasterOut|MonitorOut|Auditioner|FoldbackBus|SurroundMaster|VBMAny)
	};

	static const Flag AllStripables; /* mask to use for any route or VCA (but not auditioner) */
	static const Flag MixerStripables; /* mask to use for any route or VCA (but not auditioner or foldbackbus) */
	static const Flag AllRoutes; /* mask to use for any route include master+monitor, but not auditioner */
	static const Flag MixerRoutes; /* mask to use for any route include master+monitor, but not auditioner or foldbackbus*/
	static const Flag Route;     /* mask for any route (bus or track */
	static const Flag Track;     /* mask to use for any track */
	static const Flag Bus;       /* mask to use for any bus */
	static const Flag MidiIndicatingFlags; /* MidiTrack or MidiBus */

	typedef uint32_t order_t;
	typedef uint32_t color_t;

	PresentationInfo (Flag f);
	PresentationInfo (order_t o, Flag f);
	PresentationInfo (PresentationInfo const &);

	static const order_t max_order;

	PresentationInfo::Flag flags() const { return _flags; }
	order_t  order() const { return _order; }
	color_t  color() const { return _color; }

	bool color_set() const;

	void set_color (color_t);
	void set_hidden (bool yn);
	void set_trigger_track (bool yn);
	void set_flags (Flag f) { _flags = f; }

	bool order_set() const { return _flags & OrderSet; }

	int selection_cnt() const { return _selection_cnt; }

	bool hidden() const { return _flags & Hidden; }
	bool trigger_track () const { return _flags & TriggerTrack; }
	bool special(bool with_master = true) const { return _flags & ((with_master ? MasterOut : 0)|SurroundMaster|MonitorOut|Auditioner); }

	bool flag_match (Flag f) const {
		/* no flags, match all */

		if (f == Flag (0)) {
			return true;
		}

		if (f & StatusMask) {
			/* status bits set, must match them */
			if ((_flags & StatusMask) != (f & StatusMask)) {
				return false;
			}
		}

		/* Generic flags in f, match the right stuff */

		if (f == Bus && (_flags & Bus)) {
			/* some kind of bus */
			return true;
		}
		if (f == Track && (_flags & Track)) {
			/* some kind of track */
			return true;
		}
		if (f == Route && (_flags & Route)) {
			/* any kind of route, but not master, monitor in
			   or auditioner.
			 */
			return true;
		}

		if (f == AllRoutes && (_flags & AllRoutes)) {
			/* any kind of route, but not auditioner. Ask for that
			   specifically.
			*/
			return true;
		}

		if (f == AllStripables && (_flags & AllStripables)) {
			/* any kind of stripable, but not auditioner. Ask for that
			   specifically.
			*/
			return true;
		}

		/* check for any matching type bits.
		 *
		 * Do comparisoon without status mask or order set bits - we
		 * already checked that above.
		 */

		return ((f & TypeMask) & _flags);
	}

	int set_state (XMLNode const&, int);
	XMLNode& get_state () const;

	bool operator==(PresentationInfo const& other) {
		return (_order == other.order()) && (_flags == other.flags());
	}

	bool operator!=(PresentationInfo const& other) {
		return (_order != other.order()) || (_flags != other.flags());
	}

	PresentationInfo& operator= (PresentationInfo const& other);

	static Flag get_flags (XMLNode const& node);
	static Flag get_flags2X3X (XMLNode const& node);
	static std::string state_node_name;

	/* for things concerned about *any* PresentationInfo.
	 */

	static PBD::Signal<void(PBD::PropertyChange const &)> Change;
	static void send_static_change (const PBD::PropertyChange&);

	static void make_property_quarks ();

  protected:
	friend class ChangeSuspender;
	static void suspend_change_signal ();
	static void unsuspend_change_signal ();

  public:
	class ChangeSuspender {
          public:
		ChangeSuspender() {
			PresentationInfo::suspend_change_signal ();
		}
		~ChangeSuspender() {
			PresentationInfo::unsuspend_change_signal ();
		}
	};

  protected:
	friend class Stripable;
	void set_order (order_t order);

  private:
	order_t _order;
	Flag    _flags;
	color_t _color;
	int     _selection_cnt;

	static PBD::PropertyChange _pending_static_changes;
	static PBD::Mutex static_signal_lock;
	static std::atomic<int>   _change_signal_suspended;

	static int selection_counter;
};

}

namespace std {
std::ostream& operator<<(std::ostream& o, ARDOUR::PresentationInfo const& rid);
}

