/*
    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.
*/

#ifndef _SNAP_GRID_H_
#define _SNAP_GRID_H_

#include "RulerScale.h"

#include <map>

namespace Rosegarden {

/**
 * SnapGrid is a class that maps x-coordinate onto time, using a
 * RulerScale to get the mapping but constraining the results to a
 * discrete set of suitable times.
 *
 * (It also snaps y-coordinates, but that bit isn't very interesting.)
 */

class SnapGrid
{
public:
    /**
     * Construct a SnapGrid that uses the given RulerScale for
     * x-coordinate mappings and the given ysnap for y-coords.
     * If ysnap is zero, y-coords are not snapped at all.
     */
    SnapGrid(RulerScale *rulerScale, int ysnap = 0);

    static const timeT NoSnap;
    static const timeT SnapToBar;
    static const timeT SnapToBeat;
    static const timeT SnapToUnit;

    enum SnapDirection { SnapEither, SnapLeft, SnapRight };

    /**
     * Set the snap size of the grid to the given time.
     * The snap time must be positive, or else one of the
     * special constants NoSnap, SnapToBar, SnapToBeat or
     * SnapToUnit.
     * The default is SnapToBeat.
     */
    void setSnapTime(timeT snap);

    /**
     * Return the snap size of the grid, at the given x-coordinate.
     * (The x-coordinate is required in case the built-in snap size is
     * SnapToBar, SnapToBeat or SnapToUnit, in which case we need to
     * know the current time signature.)  Returns zero for NoSnap.
     */
    timeT getSnapTime(double x) const;

    /**
     * Return the snap setting -- the argument that was passed to
     * setSnapTime.  This differs from getSnapTime, which interprets
     * the NoSnap, SnapToBar, SnapToBeat and SnapToUnit settings to
     * return actual timeT values; instead this function returns those
     * actual constants if set.
     */
    timeT getSnapSetting() const;

    /**
     * Return the snap size of the grid, at the given time.  (The time
     * is required in case the built-in snap size is SnapToBar,
     * SnapToBeat or SnapToUnit, in which case we need to know the
     * current time signature.)  Returns zero for NoSnap.
     */
    timeT getSnapTime(timeT t) const;

    /**
     * Snap a given x-coordinate to the nearest time on the grid.  Of
     * course this also does x-to-time conversion, so it's useful even
     * in NoSnap mode.  If the snap time is greater than the bar
     * duration at this point, the bar duration will be used instead.
     *
     * If d is SnapLeft or SnapRight, a time to the left or right
     * respectively of the given coordinate will be returned;
     * otherwise the nearest time on either side will be returned.
     */
    timeT snapX(double x, SnapDirection d = SnapEither) const;

    /**
     * Snap a given time to the nearest time on the grid.  Unlike
     * snapX, this is not useful in NoSnap mode.  If the snap time is
     * greater than the bar duration at this point, the bar duration
     * will be used instead.
     *
     * If d is SnapLeft or SnapRight, a time to the left or right
     * respectively of the given coordinate will be returned;
     * otherwise the nearest time on either side will be returned.
     */
    timeT snapTime(timeT t, SnapDirection d = SnapEither) const;

    /**
     * Snap a given y-coordinate to the nearest lower bin coordinate.
     */
    int snapY(int y) const {
        if (m_ysnap == 0) return y;
	return getYBinCoordinate(getYBin(y));
    }

    /**
     * Return the bin number for the given y-coordinate.
     */
    int getYBin(int y) const;

    /**
     * Return the y-coordinate of the grid line at the start of the
     * given bin.
     */
    int getYBinCoordinate(int bin) const;

    /**
     * Set the default vertical step.  This is used as the height for
     * bins that have no specific height multiple set, and the base
     * height for bins that have a multiple.  Setting the Y snap here
     * is equivalent to specifying it in the constructor.
     */
    void setYSnap(int ysnap) {
	m_ysnap = ysnap;
    }

    /**
     * Retrieve the default vertical step.
     */
    int getYSnap() const {
        return m_ysnap;
    }

    /**
     * Set the height multiple for a specific bin.  The bin will be
     * multiple * ysnap high.  The default is 1 for all bins.
     */
    void setBinHeightMultiple(int bin, int multiple) {
	m_ymultiple[bin] = multiple;
    }
    
    /**
     * Retrieve the height multiple for a bin.
     */
    int getBinHeightMultiple(int bin) {
	if (m_ymultiple.find(bin) == m_ymultiple.end()) return 1;
	return m_ymultiple[bin];
    }

    RulerScale *getRulerScale() {
        return m_rulerScale;
    }

    const RulerScale *getRulerScale() const {
        return m_rulerScale;
    }

protected:
    RulerScale *m_rulerScale; // I don't own this
    timeT m_snapTime;
    int m_ysnap;
    std::map<int, int> m_ymultiple;
};

}

#endif
