/*
 * File:     TimeRuler.java
 * Project:  MPI Linguistic Application
 * Date:     03 April 2006
 *
 * Copyright (C) 2001-2006  Max Planck Institute for Psycholinguistics
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package mpi.eudico.client.annotator.viewer;

import mpi.eudico.client.util.TimeFormatter;

import java.awt.Font;
import java.awt.Graphics2D;

import javax.swing.SwingConstants;


/**
 * Paints a ruler denoting time units.
 * In verion 0.2 the paint method has been changed such that rounded values
 * are used for the labels as much as possible. The old method of labels at
 * a fixed interval has been moved to a new method paintAtFixedInterval.
 * In version 0.3 float precision for the msPerPixel has been introduced.
 *
 * @author Han Sloetjes
 * @version 0.1 9/7/2003
 * @version 0.2 & 0.3 oct-nov 2005
 */
public class TimeRuler {
    private Font font;

    /** Default distance between two small ticks */
    private int minorTickSpacing;

    /** Default distance between two large ticks */
    private int majorTickSpacing;

    /** Average width of a time label */
    private int avgLabelWidth;
    private String defLabel;
    private int height;

    /** floats for more accurate calculations */
    private float majorTick;
    private float minorTick;

    // store the last used label step distance
    private int step;

    // store the last used msPerPixels to avoid unnecessary calculations
    // in the paint method
    private float lastUsedMspp = -1;

    /** base values for round label vakues */
    private final int[] stepbase = new int[] { 10, 20, 50 };

    /** counter for the Math.pow loop over the stepbase values */
    private final int power = 4;

    /**
     * Constructs a TimeRuler using the specified Font and a default label.<br>
     * This label should reflect the format that will be used for the time
     * labels.
     *
     * @param font the font to use for the labels
     * @param defLabel the default time label, e.g. 00:00:000
     */
    public TimeRuler(Font font, String defLabel) {
        this.font = font;
        this.defLabel = defLabel;
        minorTickSpacing = 10; //every 10 pixels
        majorTickSpacing = 100; //every 100 pixels
        height = font.getSize() + 4 + 4;
        majorTick = majorTickSpacing;
        minorTick = minorTickSpacing;
    }

    /**
     * Paints a time ruler to the specified Graphics2D object.<br>
     * A begintime (for an interval) and a width in pixels should be provided,
     * as well as the number of milliseconds per pixel (resolution). The
     * position can be top or bottom. Labels and ticks are always painted with
     * a fixed interval, defined by  <code>majorTickSpacing</code> and
     * <code>minorTickSpacing</code>, by  default 100 and 10 pixels.
     *
     * @param g2d the Graphics2D object
     * @param beginTime the (interval) begin time
     * @param width the width in pixels to use for painting
     * @param msPerPixel the resolution in milliseconds per pixel
     * @param position either SwingConstants.TOP or SwingConstants.BOTTOM
     */
    public void paintAtFixedInterval(Graphics2D g2d, long beginTime, int width,
        float msPerPixel, int position) {
        g2d.setFont(font);
        avgLabelWidth = g2d.getFontMetrics(font).stringWidth(defLabel);

        // System.out.println("Ms pp: " + msPerPixel);
        int step = (int) (majorTickSpacing * msPerPixel);
        long firstMarker = step * (beginTime / step);
        int firstLine = (int) (firstMarker / msPerPixel);
        int extent = firstLine + width + majorTickSpacing;

        for (int i = firstLine; i <= extent; i += minorTickSpacing) {
            switch (position) {
            case SwingConstants.TOP:
                g2d.drawLine(i, 0, i, 2);

                break;

            case SwingConstants.BOTTOM:
                g2d.drawLine(i, height - 2, i, height);

                break;

            default:}
        }

        // major ticks and labels
        for (int j = firstLine; j <= extent; j += majorTickSpacing) {
            switch (position) {
            case SwingConstants.TOP:
                g2d.drawLine(j, 0, j, 5);
                g2d.drawString(TimeFormatter.toString(firstMarker),
                    j - (avgLabelWidth / 2), height - 2);

                break;

            case SwingConstants.BOTTOM:
                g2d.drawString(TimeFormatter.toString(firstMarker),
                    j - (avgLabelWidth / 2), font.getSize() + 2);
                g2d.drawLine(j, height - 5, j, height);

                break;

            default:}

            //firstMarker += (majorTickSpacing * msPerPixel);
            firstMarker += step;

            //System.out.println("Next label: " + firstMarker);
        }
    }

    /**
     * Paints a time ruler to the specified Graphics2D object.<br>
     * A begintime (for an interval) and a width in pixels should be provided,
     * as well as the number of milliseconds per pixel (resolution). The
     * position can be top or bottom. Labels and ticks are <b>not</b> always
     * painted with a fixed interval.  This method tries to paint round time
     * values (whole seconds, hundreds/thousands of  milliseconds etc.
     * depending on the resolution) where the <code>majorTickSpacing</code>
     * value serves as the minimum label distance.
     *
     * @param g2d the Graphics2D object
     * @param beginTime the (interval) begin time
     * @param width the width in pixels to use for painting
     * @param msPerPixel the resolution in milliseconds per pixel
     * @param position either SwingConstants.TOP or SwingConstants.BOTTOM
     */
    public void paint(Graphics2D g2d, long beginTime, int width,
        float msPerPixel, int position) {
        g2d.setFont(font);
        avgLabelWidth = g2d.getFontMetrics(font).stringWidth(defLabel);

        // try to paint labels for some rounded values, depending on 
        // the amount of msPerPixel, ignoring the defaults for minor- and
        // majortickspacing
        // recalculate majorTick, minorTick and label step
        if (msPerPixel != lastUsedMspp) {
            int oldStep = (int) (majorTickSpacing * msPerPixel);
            step = getRoundedStep(oldStep);
            majorTick = majorTickSpacing * ((float) step / oldStep);
            minorTick = majorTick / ((float) majorTickSpacing / minorTickSpacing);
            lastUsedMspp = msPerPixel;
        }

        // begin with a marker that is smaller than the begintime
        long firstMarker = step * (beginTime / step);
        int firstLine = (int) (firstMarker / msPerPixel);
        int extent = firstLine + width + (int) majorTick;
        int x;

        for (int i = firstLine, j = 0; i <= extent; j++) {
            x = firstLine + (int) Math.round(j * minorTick);

            switch (position) {
            case SwingConstants.TOP:
                g2d.drawLine(x, 0, x, 2);

                break;

            case SwingConstants.BOTTOM:
                g2d.drawLine(x, height - 2, x, height);

                break;

            default:}

            i = x;
        }

        for (int j = firstLine, i = 0; j <= extent; i++) {
            x = firstLine + (int) Math.round(i * majorTick);

            switch (position) {
            case SwingConstants.TOP:
                g2d.drawLine(x, 0, x, 5);
                g2d.drawString(TimeFormatter.toString(firstMarker),
                    x - (avgLabelWidth / 2), height - 2);

                break;

            case SwingConstants.BOTTOM:
                g2d.drawString(TimeFormatter.toString(firstMarker),
                    x - (avgLabelWidth / 2), font.getSize() + 2);
                g2d.drawLine(x, height - 5, x, height);

                break;

            default:}

            //firstMarker += Math.round(majorTick * msPerPixel);
            firstMarker += step;
            j = x;

            //System.out.println("Next label: " + firstMarker);
        }
    }

    /**
     * Sets the font for the time label values.
     *
     * @param font the font for the time label values
     */
    public void setFont(Font font) {
        this.font = font;
        height = font.getSize() + 4 + 4;
    }

    /**
     * Returns the font in use for the time labels.
     *
     * @return the font in use for the time labels
     */
    public Font getFont() {
        return font;
    }

    /**
     * Returns the next, rounded up step value that will assure rounded time
     * values and non overlapping time labels.
     *
     * @param inStep the calculated step size, the basis for the new step
     *
     * @return the new step value
     */
    private int getRoundedStep(int inStep) {
        int step = inStep;
        int one;
        int two;
        int five;

        for (int i = 1; i <= power; i++) {
            one = (int) (stepbase[0] * Math.pow(10, i));
            two = (int) (stepbase[1] * Math.pow(10, i));
            five = (int) (stepbase[2] * Math.pow(10, i));

            if ((step == one) || (step == two) || (step == five)) {
                break;
            }

            if (step < one) {
                if (i == 0) {
                    step = one;

                    break;
                } else {
                    continue;
                }
            } else if (step > five) {
                if (i == power) {
                    step = five;

                    break;
                } else {
                    if (step < (int) (stepbase[0] * Math.pow(10, i + 1))) {
                        step = (int) (stepbase[0] * Math.pow(10, i + 1));

                        break;
                    }
                }
            } else if ((step > one) && (step < two)) {
                step = two;

                break;
            } else if ((step > two) && (step < five)) {
                step = five;

                break;
            }
        }

        return step;
    }

    /**
     * Returns the distance in pixels between consecutive time labels or major
     * tick marks.  100 pixels by default. It depends on the value of the
     * 'milliseconds per pixel'  parameter passed to the paint method how many
     * milliseconds a 'major tick'  represents.
     *
     * @return distance in pixels between major ticks
     */
    public int getMajorTickSpacing() {
        return majorTickSpacing;
    }

    /**
     * Returns the distance in pixels between minor tick marks, a subdivision
     * of the  major ticks. 10 pixels by default.
     *
     * @return distance in pixels between minor ticks
     */
    public int getMinorTickSpacing() {
        return minorTickSpacing;
    }

    /**
     * Sets the distance in pixels between consecutive time labels or major
     * tick  marks.
     *
     * @param spacing the new distance in pixels between major ticks
     */
    public void setMajorTickSpacing(int spacing) {
        majorTickSpacing = spacing;

        // force recalculations
        lastUsedMspp = -1;
    }

    /**
     * Sets the distance in pixels between minor tick marks.
     *
     * @param spacing the new space in pixels between minor ticks
     */
    public void setMinorTickSpacing(int spacing) {
        minorTickSpacing = spacing;

        // force recalculations
        lastUsedMspp = -1;
    }

    /**
     * Returns the height of the ruler, which is based on the size of the font.
     *
     * @return the height of the ruler
     */
    public int getHeight() {
        return height;
    }
}
