/*
 * File:     WavePart.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 java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;


/**
 * Stores the waveform geometry of an interval  of an audiofile. This class is
 * not threadsafe.
 *
 * @author Han Sloetjes
 * @version June 2003
 */
public class WavePart {
    /** use GeneralPath objects to store coordinates */
    public static final int GENERAL_PATH_MODE = 0;

    /** use int arrays to store y coordinates */
    public static final int INT_ARRAY_MODE = 1;

    /** The interval starttime of this WavePart */
    private long startTime;

    /** The interval stoptime of this WavePart */
    private long stopTime;
    private boolean useTwoChannels;

    /**
     * The path of <br>
     * - the one channel of a mono audiofile<br>
     * - the first channel of a stereo audiofile<br>
     * - the two, merged channels of a stereo audiofile
     */
    private GeneralPath firstPath;

    /** The path of the second channel of a stereo audiofile */
    private GeneralPath secondPath;

    /** the index of the first sample to load */
    int startSample;

    /** the max number of pixels to paint in the current interval */
    int extent;

    /** four arrays to store top and bottom values for two channels */
    private int[] leftTops;
    private int[] leftBottoms;
    private int[] rightTops;
    private int[] rightBottoms;
    private int mode = GENERAL_PATH_MODE;

    /**
     * No arg constructor, initializes all fields.
     */
    public WavePart() {
        this(0L, 0L, true);
    }

    /**
     * Constructor, initializes this WavePart in the specified mode.
     *
     * @param mode DOCUMENT ME!
     */
    public WavePart(int mode) {
        this(0L, 0L, true, mode);
    }

    /**
     * Constructs a WavePart for the interval specified by the given
     * parameters. Assumes that two channels can be used.
     *
     * @param startTime the interval starttime
     * @param stopTime the interval stoptime
     */
    public WavePart(long startTime, long stopTime) {
        this(startTime, stopTime, true);
    }

    /**
     * Creates a new WavePart instance
     *
     * @param startTime DOCUMENT ME!
     * @param stopTime DOCUMENT ME!
     * @param useTwoChannels DOCUMENT ME!
     */
    public WavePart(long startTime, long stopTime, boolean useTwoChannels) {
        this.startTime = startTime;
        this.stopTime = stopTime;
        firstPath = new GeneralPath();
        setUseTwoChannels(useTwoChannels);
    }

    /**
     * Creates a new WavePart instance
     *
     * @param startTime DOCUMENT ME!
     * @param stopTime DOCUMENT ME!
     * @param useTwoChannels DOCUMENT ME!
     * @param mode DOCUMENT ME!
     */
    public WavePart(long startTime, long stopTime, boolean useTwoChannels,
        int mode) {
        this.startTime = startTime;
        this.stopTime = stopTime;
        setMode(mode);
        setUseTwoChannels(useTwoChannels);
    }

    /**
     * DOCUMENT ME!
     *
     * @return the path for the first channel
     */
    public GeneralPath getFirstPath() {
        return firstPath;
    }

    /**
     * DOCUMENT ME!
     *
     * @return the path for the second channel
     */
    public GeneralPath getSecondPath() {
        return secondPath;
    }

    /**
     * DOCUMENT ME!
     *
     * @return the current interval starttime
     */
    public long getStartTime() {
        return startTime;
    }

    /**
     * DOCUMENT ME!
     *
     * @param startTime the new interval starttime
     */
    public void setStartTime(long startTime) {
        this.startTime = startTime;
    }

    /**
     * DOCUMENT ME!
     *
     * @return the current interval stoptime
     */
    public long getStopTime() {
        return stopTime;
    }

    /**
     * DOCUMENT ME!
     *
     * @param stopTime the new interval stoptime
     */
    public void setStopTime(long stopTime) {
        this.stopTime = stopTime;
    }

    /**
     * The mode of a WavePart determines how the audio data should be stored.
     *
     * @return the mode of this WavePart
     */
    public int getMode() {
        return mode;
    }

    /**
     * DOCUMENT ME!
     *
     * @param mode the new mode for this WavePart
     */
    public void setMode(int mode) {
        if (mode == INT_ARRAY_MODE) {
            this.mode = mode;
        } else {
            this.mode = GENERAL_PATH_MODE;

            if (firstPath == null) {
                firstPath = new GeneralPath();
            }
        }
    }

    /**
     * DOCUMENT ME!
     *
     * @return true if two channels are in use, false otherwise
     */
    public boolean isUseTwoChannels() {
        return useTwoChannels;
    }

    /**
     * Sets whether or not two channels are to be used.
     *
     * @param useTwoChannels the new value for useTwoChannels
     */
    public void setUseTwoChannels(boolean useTwoChannels) {
        this.useTwoChannels = useTwoChannels;

        if (!useTwoChannels) {
            secondPath = null;
            rightTops = null;
            rightBottoms = null;
        } else if ((mode == GENERAL_PATH_MODE) && (secondPath == null)) {
            secondPath = new GeneralPath();
        }
    }

    /**
     * Sets the current interval time, the first sample (startTime /
     * msPerPixel) and  the size (in pixels) of the current interval.
     *
     * @param startTime the new start time
     * @param stopTime the new end time
     * @param startSample the first sample as a x coordinate value
     * @param size the number of pixels (the minimal size of the int array)
     * @param extent the max number of pixels to paint
     */
    public void setInterval(long startTime, long stopTime, int startSample,
        int size, int extent) {
        this.startTime = startTime;
        this.stopTime = stopTime;
        this.startSample = startSample;
        this.extent = extent;

        if (mode == INT_ARRAY_MODE) {
            if ((leftTops == null) || (leftTops.length < size)) {
                leftTops = new int[size];
                leftBottoms = new int[size];
            }

            if (useTwoChannels &&
                    ((rightTops == null) || (rightTops.length < size))) {
                rightTops = new int[size];
                rightBottoms = new int[size];
            }
        }
    }

    /**
     * Add a line to the left channel.
     *
     * @param x the x coordinate
     * @param top the y coordinate of the max value
     * @param bottom the y coordinate of the min value
     */
    public void addLineToFirstChannel(int x, int top, int bottom) {
        if (mode == GENERAL_PATH_MODE) {
            firstPath.moveTo(x, top);
            firstPath.lineTo(x, bottom);
        } else {
            if (((x - startSample) >= 0) &&
                    ((x - startSample) < leftTops.length)) {
                leftTops[x - startSample] = top;
                leftBottoms[x - startSample] = bottom;
            }
        }
    }

    /**
     * Add a line to the right channel.
     *
     * @param x the x coordinate
     * @param top the y coordinate of the max value
     * @param bottom the y coordinate of the min value
     */
    public void addLineToRightChannel(int x, int top, int bottom) {
        if (mode == GENERAL_PATH_MODE) {
            secondPath.moveTo(x, top);
            secondPath.lineTo(x, bottom);
        } else {
            if (((x - startSample) >= 0) &&
                    ((x - startSample) < rightTops.length)) {
                rightTops[x - startSample] = top;
                rightBottoms[x - startSample] = bottom;
            }
        }
    }

    /**
     * Paint either the path or the data contained in the int array for the
     * left channel.
     *
     * @param g2d the g=Graphics context to render to
     * @param at the transformation to use for the channel
     */
    public void paintLeftChannel(Graphics2D g2d, AffineTransform at) {
        if (mode == GENERAL_PATH_MODE) {
            g2d.draw(firstPath.createTransformedShape(at));
        } else {
            float scale = (float) at.getScaleY();

            if (leftTops != null) {
                for (int i = 0; (i < leftTops.length) && (i < (extent - 1));
                        i++) {
                    g2d.drawLine(i + startSample, (int) (leftTops[i] * scale),
                        i + startSample, (int) (leftBottoms[i] * scale));
                }
            }
        }
    }

    /**
     * Paint either the path or the data contained in the int array for the
     * left channel.
     *
     * @param g2d the g=Graphics context to render to
     * @param at the transformation to use for the channel
     */
    public void paintRightChannel(Graphics2D g2d, AffineTransform at) {
        if (mode == GENERAL_PATH_MODE) {
            g2d.draw(secondPath.createTransformedShape(at));
        } else {
            float scale = (float) at.getScaleY();

            if (rightTops != null) {
                for (int i = 0; (i < rightTops.length) && (i < (extent - 1));
                        i++) {
                    g2d.drawLine(i + startSample, (int) (rightTops[i] * scale),
                        i + startSample, (int) (rightBottoms[i] * scale));
                }
            }
        }
    }

    /**
     * Checks whether the specified time is within the current  interval
     * starttime and stoptime, inclusive.
     *
     * @param time the time to check
     *
     * @return true if <code>time</code> is within the current interval,  false
     *         otherwise
     */
    public boolean contains(long time) {
        if ((time >= startTime) && (time <= stopTime)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Checks whether the specified time interval is within  the current
     * interval starttime and stoptime, inclusive.
     *
     * @param fromTime the starttime of the interval to check
     * @param toTime the stoptime of the interval to check
     *
     * @return true if the given time interval is within the current interval,
     *         false otherwise
     */
    public boolean contains(long fromTime, long toTime) {
        if ((fromTime >= startTime) && (toTime <= stopTime)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Clears the contents of both GeneralPath objects, if present.
     */
    public void reset() {
        if (mode == GENERAL_PATH_MODE) {
            if (firstPath != null) {
                firstPath.reset();
            }

            if (secondPath != null) {
                secondPath.reset();
            }
        }
    }

    /**
     * DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public String toString() {
        StringBuffer b = new StringBuffer();
        b.append("WavePart - ");
        b.append("start time: ");
        b.append(startTime);
        b.append(" stop time: ");
        b.append(stopTime);
        b.append(" ms.");

        return b.toString();
    }
}
