/*
  Copyright (C) 2002-2010 Paul Davis

  This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU Lesser 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 Lesser General Public
  License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with this program; if not, write to the Free Software Foundation,
  Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#pragma once

#include <ostream>
#include <istream>
#include <sstream>
#include <stdint.h>
#include <iomanip>
#include <exception>
#include <cmath>
#include <limits>
#include <cstdio>

#include "pbd/failed_constructor.h"
#include "pbd/string_convert.h"

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

namespace Temporal {

struct IllegalBBTTimeException : public std::exception {
	virtual const char* what() const throw() { return "illegal BBT time (bars or beats were zero, or ticks was too large)"; }
};

struct BBT_Offset;

/** Bar, Beat, Tick Time (i.e. Tempo-Based Time) */
struct LIBTEMPORAL_API BBT_Time
{
	/* note that it is illegal for BBT_Time to have bars==0 or
	 * beats==0. The "neutral" or "default" value is 1|1|0
	 */

	int32_t bars;
	int32_t beats;
	int32_t ticks;

	int64_t as_integer() const;
	static BBT_Time from_integer (int64_t);

	bool is_bar() const { return beats == 1 && ticks == 0; }
	bool is_beat() const { return ticks == 0; }

	BBT_Time () : bars (1), beats (1), ticks (0) {}
	BBT_Time (int32_t ba, uint32_t be, uint32_t t) : bars (ba), beats (be), ticks (t) {
		if (!bars || !beats) {
			throw IllegalBBTTimeException();
		}
	}

	bool operator< (const BBT_Time& other) const {
		return bars < other.bars ||
			(bars == other.bars && beats < other.beats) ||
			(bars == other.bars && beats == other.beats && ticks < other.ticks);
	}

	bool operator<= (const BBT_Time& other) const {
		return bars < other.bars ||
			(bars <= other.bars && beats < other.beats) ||
			(bars <= other.bars && beats <= other.beats && ticks <= other.ticks);
	}

	bool operator> (const BBT_Time& other) const {
		return bars > other.bars ||
			(bars == other.bars && beats > other.beats) ||
			(bars == other.bars && beats == other.beats && ticks > other.ticks);
	}

	bool operator>= (const BBT_Time& other) const {
		return bars > other.bars ||
			(bars >= other.bars && beats > other.beats) ||
			(bars >= other.bars && beats >= other.beats && ticks >= other.ticks);
	}

	bool operator== (const BBT_Time& other) const {
		return bars == other.bars && beats == other.beats && ticks == other.ticks;
	}

	bool operator!= (const BBT_Time& other) const {
		return bars != other.bars || beats != other.beats || ticks != other.ticks;
	}

	bool operator<  (const BBT_Offset& other) const;
	bool operator<= (const BBT_Offset& other) const;
	bool operator>  (const BBT_Offset& other) const;
	bool operator>= (const BBT_Offset& other) const;
	bool operator== (const BBT_Offset& other) const;
	bool operator!= (const BBT_Offset& other) const;

	/* it would be nice to provide +,-,* and / operators for BBT_Time but
	 * this math requires knowledge of the meter (time signature) used to
	 * define 1 bar, and so cannot be carried out with only two BBT_Time
	 * values.
	 */

	/* cannot implement round_to_*() or round_up_tp_*() without knowing meter (time
	 * signature) information, since it requires knowing how many beats
	 * are in a bar, in order to decide if we are closer to the previous or
	 * next bar time.
	 */

	BBT_Time round_down_to_beat () const { return BBT_Time (bars, beats, 0); }
	BBT_Time round_down_to_bar () const { return BBT_Time (bars, 1, 0); }

	BBT_Time next_bar () const { return (bars == -1) ? BBT_Time (1, 1, 0) : BBT_Time (bars+1, 1, 0); }
	BBT_Time prev_bar () const { return (bars == 1)  ? BBT_Time (-1, 1, 0) : BBT_Time (bars-1, 1, 0); }

	void print_padded (std::ostream& o) const {
		o << std::setfill ('0') << std::right
		  << std::setw (3) << bars << "|"
		  << std::setw (2) << beats << "|"
		  << std::setw (4) << ticks;
	}

	std::string str () const {
		std::ostringstream os;
		os << bars << '|' << beats << '|' << ticks;
		return os.str ();
	}

};

struct LIBTEMPORAL_API BBT_Offset
{
	int32_t bars;
	int32_t beats;
	int32_t ticks;

	/* this is a variant for which bars==0 and/or beats==0 is legal. It
	 * represents an offset from a given BBT_Time and is used when doing
	 * add/subtract operations on a BBT_Time.
	 */

	BBT_Offset () : bars (0), beats (0), ticks (0) {}
	BBT_Offset (int32_t ba, uint32_t be, uint32_t t) : bars (ba), beats (be), ticks (t) {}
	BBT_Offset (BBT_Time const & bbt) : bars (bbt.bars), beats (bbt.beats), ticks (bbt.ticks) {}
	explicit BBT_Offset (double beats);

	/* unlike BBT_Time, we can define +,-,* and / operators for BBT_Offset
	 * because there is no requirement that the result is "well-formed" or
	 * reflect the structure of a tempo map. It is just as valid to talk
	 * about an offset of 18 beats as an offset of 4 bars and 2 beats.
	 */

	BBT_Offset operator+(const BBT_Offset& other) const {
		return BBT_Offset (bars+other.bars, beats+other.beats, ticks+other.ticks);
	}

	BBT_Offset operator-() const {
		return BBT_Offset (-bars, -beats, -ticks);
	}

	BBT_Offset operator-(const BBT_Offset& other) const {
		return BBT_Offset (bars-other.bars, beats-other.beats, ticks-other.ticks);
	}

	BBT_Offset & operator+=(const BBT_Offset& other) {
		bars += other.bars;
		beats += other.beats;
		ticks += other.ticks;
		return *this;
	}

	BBT_Offset & operator-=(const BBT_Offset& other) {
		bars -= other.bars;
		beats -= other.beats;
		ticks -= other.ticks;
		return *this;
	}

	BBT_Offset & operator*=(int factor) {
		bars *= factor;
		beats *= factor;
		ticks *= factor;
		return *this;
	}

	BBT_Offset & operator*=(double factor) {
		bars = (int32_t) lrint (bars * factor);
		beats = (int32_t) lrint (beats * factor);
		ticks = (int32_t) lrint (ticks * factor);
		return *this;
	}

	BBT_Offset & operator/=(int factor) {
		bars /= factor;
		beats /= factor;
		ticks /= factor;
		return *this;
	}

	BBT_Offset & operator/=(double factor) {
		bars = (int32_t) lrint (bars / factor);
		beats = (int32_t) lrint (beats / factor);
		ticks = (int32_t) lrint (ticks / factor);
		return *this;
	}

	bool operator< (const BBT_Offset& other) const {
		return bars < other.bars ||
			(bars == other.bars && beats < other.beats) ||
			(bars == other.bars && beats == other.beats && ticks < other.ticks);
	}

	bool operator<= (const BBT_Offset& other) const {
		return bars < other.bars ||
			(bars <= other.bars && beats <= other.beats) ||
			(bars <= other.bars && beats <= other.beats && ticks <= other.ticks);
	}

	bool operator> (const BBT_Offset& other) const {
		return bars > other.bars ||
			(bars == other.bars && beats > other.beats) ||
			(bars == other.bars && beats == other.beats && ticks > other.ticks);
	}

	bool operator>= (const BBT_Offset& other) const {
		return bars > other.bars ||
			(bars >= other.bars && beats >= other.beats) ||
			(bars >= other.bars && beats >= other.beats && ticks >= other.ticks);
	}

	bool operator== (const BBT_Offset& other) const {
		return bars == other.bars && beats == other.beats && ticks == other.ticks;
	}

	bool operator!= (const BBT_Offset& other) const {
		return bars != other.bars || beats != other.beats || ticks != other.ticks;
	}

	operator bool() const {
		return bars == 0 && beats == 0 && ticks == 0;
	}

	std::string str () const {
		std::ostringstream os;
		os << bars << '|' << beats << '|' << ticks;
		return os.str ();
	}
};

inline bool
BBT_Time::operator< (const BBT_Offset& other) const
{
	return bars < other.bars ||
	              (bars == other.bars && beats < other.beats) ||
		(bars == other.bars && beats == other.beats && ticks < other.ticks);
}

inline bool
BBT_Time::operator<= (const BBT_Offset& other) const
{
	return bars < other.bars ||
	              (bars <= other.bars && beats <= other.beats) ||
		(bars <= other.bars && beats <= other.beats && ticks <= other.ticks);
}

inline bool
BBT_Time::operator> (const BBT_Offset& other) const
{
	return bars > other.bars ||
		(bars == other.bars && beats > other.beats) ||
		(bars == other.bars && beats == other.beats && ticks > other.ticks);
}

inline bool
BBT_Time::operator>= (const BBT_Offset& other) const
{
	return bars > other.bars ||
		(bars >= other.bars && beats >= other.beats) ||
		(bars >= other.bars && beats >= other.beats && ticks >= other.ticks);
}

inline bool
BBT_Time::operator== (const BBT_Offset& other) const
{
	return bars == other.bars && beats == other.beats && ticks == other.ticks;
}

inline bool
BBT_Time::operator!= (const BBT_Offset& other) const
{
	return bars != other.bars || beats != other.beats || ticks != other.ticks;
}

} /* end of namespace Temporal */

/* Putting these into std:: seems wrong, but g++ is unable to find them
 * otherwise
 */

namespace std {

LIBTEMPORAL_API std::ostream& operator<< (std::ostream& o, Temporal::BBT_Time const & bbt);
LIBTEMPORAL_API std::ostream& operator<< (std::ostream& o, Temporal::BBT_Offset const & bbt);
LIBTEMPORAL_API std::istream& operator>> (std::istream& i, Temporal::BBT_Time& bbt);
LIBTEMPORAL_API std::istream& operator>> (std::istream& i, Temporal::BBT_Offset& bbt);

template<>
struct numeric_limits<Temporal::BBT_Time> {
	static Temporal::BBT_Time lowest() {
		return Temporal::BBT_Time(1, 1, 0);
	}
	static Temporal::BBT_Time min() {
		return Temporal::BBT_Time(1, 1, 0);
	}

	static Temporal::BBT_Time max() {
		return Temporal::BBT_Time(std::numeric_limits<int32_t>::max(),
		                          std::numeric_limits<int32_t>::max(),
		                          std::numeric_limits<int32_t>::max());
	}
};

template<>
struct numeric_limits<Temporal::BBT_Offset> {
	static Temporal::BBT_Offset lowest() {
		return Temporal::BBT_Time(0, 0, 0);
	}

	static Temporal::BBT_Offset min() {
		return Temporal::BBT_Time(0, 0, 0);
	}

	static Temporal::BBT_Time max() {
		return Temporal::BBT_Time(std::numeric_limits<int32_t>::max(),
		                          std::numeric_limits<int32_t>::max(),
		                          std::numeric_limits<int32_t>::max());
	}
};


} /* end of namespace std */

namespace PBD {

template<>
inline bool to_string (Temporal::BBT_Time val, std::string & str)
{
	std::ostringstream ostr;
	ostr << val;
	str = ostr.str();
	return true;
}

template<>
inline std::string to_string (Temporal::BBT_Time val)
{
	std::ostringstream ostr;
	ostr << val;
	return ostr.str();
}

template<>
inline bool string_to (std::string const & str, Temporal::BBT_Time & val)
{
	std::istringstream istr (str);
	istr >> val;
	return (bool) istr;
}

template<>
inline Temporal::BBT_Time string_to (std::string const & str)
{
	Temporal::BBT_Time tmp;
	string_to (str, tmp);
	return tmp;
}

template<>
inline bool to_string (Temporal::BBT_Offset val, std::string & str)
{
	std::ostringstream ostr;
	ostr << val;
	str = ostr.str();
	return true;
}

template<>
inline std::string to_string (Temporal::BBT_Offset val)
{
	std::ostringstream ostr;
	ostr << val;
	return ostr.str();
}

template<>
inline bool string_to (std::string const & str, Temporal::BBT_Offset & val)
{
	std::istringstream istr (str);
	istr >> val;
	return (bool) istr;
}

template<>
inline Temporal::BBT_Offset string_to (std::string const & str)
{
	Temporal::BBT_Offset tmp;
	string_to (str, tmp);
	return tmp;
}

} /* end namespace PBD */

