/*
    Rosegarden
    A sequencer and musical notation editor.

    This program is Copyright 2000-2008
        Guillaume Laurent   <glaurent@telegraph-road.org>,
        Chris Cannam        <cannam@all-day-breakfast.com>,
        Richard Bown        <bownie@bownie.com>

    The moral right of the authors to claim authorship of this work
    has been asserted.

    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.  See the file
    COPYING included with this distribution for more information.
*/

#include "BasicQuantizer.h"
#include "BaseProperties.h"
#include "NotationTypes.h"
#include "Selection.h"
#include "Composition.h"
#include "Profiler.h"

#include <iostream>
#include <cmath>
#include <cstdio> // for sprintf
#include <ctime>

using std::cout;
using std::cerr;
using std::endl;

namespace Rosegarden
{

using namespace BaseProperties;

const std::string Quantizer::RawEventData = "";
const std::string Quantizer::DefaultTarget = "DefaultQ";
const std::string Quantizer::GlobalSource = "GlobalQ";
const std::string Quantizer::NotationPrefix = "Notation";

BasicQuantizer::BasicQuantizer(timeT unit, bool doDurations,
			       int swing, int iterate) :
    Quantizer(RawEventData),
    m_unit(unit),
    m_durations(doDurations),
    m_swing(swing),
    m_iterate(iterate)
{
    if (m_unit < 0) m_unit = Note(Note::Shortest).getDuration();
}

BasicQuantizer::BasicQuantizer(std::string source, std::string target,
			       timeT unit, bool doDurations,
			       int swing, int iterate) :
    Quantizer(source, target),
    m_unit(unit),
    m_durations(doDurations),
    m_swing(swing),
    m_iterate(iterate)
{
    if (m_unit < 0) m_unit = Note(Note::Shortest).getDuration();
}

BasicQuantizer::BasicQuantizer(const BasicQuantizer &q) :
    Quantizer(q.m_target),
    m_unit(q.m_unit),
    m_durations(q.m_durations),
    m_swing(q.m_swing),
    m_iterate(q.m_iterate)
{
    // nothing else
}

BasicQuantizer::~BasicQuantizer()
{
    // nothing
}

void
BasicQuantizer::quantizeSingle(Segment *s, Segment::iterator i) const
{
    timeT d = getFromSource(*i, DurationValue);

    if (d == 0 && (*i)->isa(Note::EventType)) {
	s->erase(i);
	return;
    }

    if (m_unit == 0) return;

    timeT t = getFromSource(*i, AbsoluteTimeValue);
    timeT d0(d), t0(t);

    timeT barStart = s->getBarStartForTime(t);

    t -= barStart;

    int n = t / m_unit;
    timeT low = n * m_unit;
    timeT high = low + m_unit;
    timeT swingOffset = (m_unit * m_swing) / 300;

    if (high - t > t - low) {
	t = low;
    } else {
	t = high;
	++n;
    }

    if (n % 2 == 1) {
	t += swingOffset;
    }
    
    if (m_durations && d != 0) {

	low = (d / m_unit) * m_unit;
	high = low + m_unit;

	if (low > 0 && (high - d > d - low)) {
	    d = low;
	} else {
	    d = high;
	}

	int n1 = n + d / m_unit;

	if (n % 2 == 0) { // start not swung
	    if (n1 % 2 == 0) { // end not swung
		// do nothing
	    } else { // end swung
		d += swingOffset;
	    }
	} else { // start swung
	    if (n1 % 2 == 0) { // end not swung
		d -= swingOffset;
	    } else {
		// do nothing
	    }
	}
    }
	
    t += barStart;

    timeT t1(t), d1(d);
    t = (t - t0) * m_iterate / 100 + t0;
    d = (d - d0) * m_iterate / 100 + d0;

    // if an iterative quantize results in something much closer than
    // the shortest actual note resolution we have, just snap it
    if (m_iterate != 100) {
	timeT close = Note(Note::Shortest).getDuration()/2;
	if (t >= t1 - close && t <= t1 + close) t = t1;
	if (d >= d1 - close && d <= d1 + close) d = d1;
    }

    if (t0 != t || d0 != d) setToTarget(s, i, t, d);
}


std::vector<timeT>
BasicQuantizer::getStandardQuantizations()
{
    checkStandardQuantizations();
    return m_standardQuantizations;
}

void
BasicQuantizer::checkStandardQuantizations()
{
    if (m_standardQuantizations.size() > 0) return;

    for (Note::Type nt = Note::Semibreve; nt >= Note::Shortest; --nt) {

	int i1 = (nt < Note::Quaver ? 1 : 0);
	for (int i = 0; i <= i1; ++i) {
	    
	    int divisor = (1 << (Note::Semibreve - nt));
	    if (i) divisor = divisor * 3 / 2;

	    timeT unit = Note(Note::Semibreve).getDuration() / divisor;
	    m_standardQuantizations.push_back(unit);
	}
    }
}    

timeT
BasicQuantizer::getStandardQuantization(Segment *s)
{
    checkStandardQuantizations();
    timeT unit = -1;

    for (Segment::iterator i = s->begin(); s->isBeforeEndMarker(i); ++i) {
	
	if (!(*i)->isa(Rosegarden::Note::EventType)) continue;
	timeT myUnit = getUnitFor(*i);
	if (unit < 0 || myUnit < unit) unit = myUnit;
    }

    return unit;
}

timeT
BasicQuantizer::getStandardQuantization(EventSelection *s)
{
    checkStandardQuantizations();
    timeT unit = -1;

    if (!s) return 0;

    for (EventSelection::eventcontainer::iterator i =
	     s->getSegmentEvents().begin();
	 i != s->getSegmentEvents().end(); ++i) {
	
	if (!(*i)->isa(Rosegarden::Note::EventType)) continue;
	timeT myUnit = getUnitFor(*i);
	if (unit < 0 || myUnit < unit) unit = myUnit;
    }

    return unit;
}

timeT
BasicQuantizer::getUnitFor(Event *e)
{	    
    timeT absTime = e->getAbsoluteTime();
    timeT myQuantizeUnit = 0;
    
    // m_quantizations is in descending order of duration;
    // stop when we reach one that divides into the note's time
    
    for (unsigned int i = 0; i < m_standardQuantizations.size(); ++i) {
	if (absTime % m_standardQuantizations[i] == 0) {
	    myQuantizeUnit = m_standardQuantizations[i];
	    break;
	}
    }

    return myQuantizeUnit;
}

std::vector<timeT>
BasicQuantizer::m_standardQuantizations;


}
