/*
 * Copyright (C) 2008 David Robillard <http://drobilla.net>
 * Copyright (C) 2000-2017 Paul Davis
 *
 * Evoral 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.
 *
 * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef __libtemporal_range_hpp__
#define __libtemporal_range_hpp__

#include <list>
#include <assert.h>
#include <iostream>

#include "temporal/visibility.h"
#include "temporal/timeline.h"
#include "temporal/types.h"

namespace Temporal {

/** end position arguments are inclusive */
template<typename T>
/*LIBTEMPORAL_API*/ OverlapType coverage_inclusive_ends (T sa, T ea, T sb, T eb) {
	/* OverlapType returned reflects how the second (B)
	 * range overlaps the first (A).
	 *
	 * The diagram shows the OverlapType of each possible relative
	 * placement of A and B.
	 *
	 * Notes:
	 *    Internal: the start and end points cannot coincide
	 *    External: the start and end points can coincide
	 *    Start: end points can coincide
	 *    End: start points can coincide
	 *
	 * Internal disallows start and end point equality, and thus implies
	 * that there are two disjoint portions of A which do not overlap B.
	 *
	 * A:    |---|
	 * B starts before A
	 * B: |-|          None
	 * B: |--|         Start
	 * B: |----|       Start
	 * B: |------|     External
	 * B: |--------|   External
	 * B starts equal to A
	 * B:    |-|       Start
	 * B:    |---|     External
	 * B:    |----|    External
	 * B starts inside A
	 * B:     |-|      Internal
	 * B:     |--|     End
	 * B:     |---|    End
	 * B starts at end of A
	 * B:        |--|  End
	 * B starts after A
	 * B:         |-|  None
	 * A:    |---|
	 */

	if (sa > ea) {
		// seems we are sometimes called with negative length ranges
		return OverlapNone;
	}

	if (sb > eb) {
		// seems we are sometimes called with negative length ranges
		return OverlapNone;
	}

	if (sb < sa) {  // B starts before A
		if (eb < sa) {
			return OverlapNone;
		} else if (eb == sa) {
			return OverlapStart;
		} else { // eb > sa
			if (eb < ea) {
				return OverlapStart;
			} else if (eb == ea) {
				return OverlapExternal;
			} else {
				return OverlapExternal;
			}
		}
	} else if (sb == sa) { // B starts equal to A
		if (eb < ea) {
			return OverlapStart;
		} else if (eb == ea) {
			return OverlapExternal;
		} else { // eb > ea
			return OverlapExternal;
		}
	} else { // sb > sa
		if (eb < ea) {
			return OverlapInternal;
		} else if (eb == ea) {
			return OverlapEnd;
		} else { // eb > ea
			if (sb < ea) { // B starts inside A
				return OverlapEnd;
			} else if (sb == ea) { // B starts at end of A
				return OverlapEnd;
			} else { // sb > ea, B starts after A
				return OverlapNone;
			}
		}
	}

	std::cerr << "unknown overlap type!" << sa << ", " << ea << "; " << sb << ", " << eb << std::endl;
	assert(!"unknown overlap type!");
	return OverlapNone;
}

/** end position arguments are inclusive */
template<typename T>
/*LIBTEMPORAL_API*/ OverlapType coverage_exclusive_ends (T sa, T eaE, T sb, T ebE)
{
	/* convert end positions to inclusive */
	return coverage_inclusive_ends (sa, eaE.decrement(), sb, ebE.decrement());
}

template<> LIBTEMPORAL_TEMPLATE_API OverlapType coverage_exclusive_ends<int64_t> (int64_t sa, int64_t eaE, int64_t sb, int64_t ebE);


class RangeList;

class LIBTEMPORAL_API Range {
  public:
	/* exclusive end semantics
	 *
	 * |--------------------------------------|
	 * ^                                      ^
	 * start                                  end
	 * 0                                      10 => length = 10, last position inside range = 9
	 * 1                                      11 => length = 10, last position inside range = 10
	 * 0                                      1  => length = 1,  last position inside range = 0
	 * 0                                      2  => length = 2,  last position inside range = 1
	 * 32                                     48 => length = 16, last position inside range = 47
	 */

	Range (timepos_t const & s, timepos_t const & e) : _start (s), _end (e) {}
	Range (samplepos_t const s, samplepos_t const e) : _start (s), _end (e) {}
	bool empty() const { return _start == _end; }

	/* length semantics: start + length = end thus end - start aka * start.distance (end)
	 * extent semantics: 1 + (end - start)
	 */

	timecnt_t length() const { return _start.distance (_end); }

	RangeList subtract (RangeList &) const;

	void set_start (timepos_t s) { _start = s; }
	void set_end (timepos_t e) { _end = e; }

	timepos_t start() const { return _start; }
	timepos_t end() const   { return _end; }

	bool operator== (Range const & other) const {
		return other._start == _start && other._end == _end;
	}

	void force_to_samples () {
		_start = timepos_t((samplepos_t)_start.samples ());
		_end   = timepos_t((samplepos_t)_end.samples ());
	}

	/** for a T, return a mapping of it into the range (used for
	 * looping). If the argument is earlier than or equal to the end of
	 * this range, do nothing.
	 */
	timepos_t squish (timepos_t const & t) const;

	/* end is exclusive */
	OverlapType coverage (timepos_t const & s, timepos_t const & e) const {
		return Temporal::coverage_exclusive_ends (_start, _end, s, e);
	}

  private:
	timepos_t _start; ///< start of the range
	timepos_t _end;   ///< end of the range (exclusive, see above)
};

typedef Range TimeRange;

class LIBTEMPORAL_API RangeList {
public:
	RangeList () : _dirty (false) {}

	typedef std::list<Range> List;

	List const & get () {
		coalesce ();
		return _list;
	}

	void add (Range const & range) {
		_dirty = true;
		_list.push_back (range);
	}

	bool empty () const {
		return _list.empty ();
	}

	void coalesce () {
		if (!_dirty) {
			return;
		}

	restart:
		for (typename List::iterator i = _list.begin(); i != _list.end(); ++i) {
			for (typename List::iterator j = _list.begin(); j != _list.end(); ++j) {

				if (i == j) {
					continue;
				}

				if (coverage_exclusive_ends (i->start(), i->end(), j->start(), j->end()) != OverlapNone) {
					i->set_start (std::min (i->start(), j->start()));
					i->set_end (std::max (i->end(), j->end()));
					_list.erase (j);
					goto restart;
				}
			}
		}

		_dirty = false;
	}

private:

	List _list;
	bool _dirty;
};

/** Type to describe the movement of a time range */
struct LIBTEMPORAL_API RangeMove {
	RangeMove (timepos_t f, timecnt_t l, timepos_t t) : from (f), length (l), to (t) {}
	timepos_t from;   ///< start of the range
	timecnt_t length; ///< length of the range
	timepos_t to;     ///< new start of the range
};

}

#endif /* __libtemporal_range_hpp__ */
