/*
 * File:     TSTrackPanelImpl.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.timeseries;

import mpi.eudico.client.annotator.Constants;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.geom.GeneralPath;

import java.util.ArrayList;
import java.util.List;


/**
 * Implementation of a track panel capable of displaying multiple time series tracks.
 */
public class TSTrackPanelImpl implements TSTrackPanel {
    /** a list of timeseries tracks */
    private List tracks;

    /** a vertical ruler */
    private TSRulerImpl vertRuler;
    private int rulerWidth;
    private float zoomLevel;
    private float msPerPixel;
    private int height;
    private int width;
    private Insets margin;
    private Rectangle trackRect;
    private int[] tickYPos = new int[3];

    /**
     *
     */
    public TSTrackPanelImpl() {
        super();

        tracks = new ArrayList(4);
        vertRuler = new TSRulerImpl();
        margin = new Insets(3, 3, 3, 3);
        rulerWidth = 40;
        vertRuler.setWidth(rulerWidth);
        trackRect = new Rectangle();
    }

    /**
     * Sets the width for the vertical ruler.
     *
     * @param rulerWidth the width for the vertical ruler
     */
    public void setRulerWidth(int rulerWidth) {
        this.rulerWidth = rulerWidth;

        vertRuler.setWidth(rulerWidth);
        trackRect.x = margin.left + rulerWidth;
    }

    /**
     * Returns the current width of the vertical ruler.
     *
     * @return the width of the vertical ruler
     */
    public int getRulerWidth() {
        return rulerWidth;
    }

    /**
     * Replaces the vertical ruler.
     *
     * @param ruler the new vertical ruler
     */
    public void setRuler(TSRuler ruler) {
        vertRuler = (TSRulerImpl) ruler;
        vertRuler.setWidth(rulerWidth);
        vertRuler.setHeight(height - margin.top - margin.bottom);
        trackRect.x = margin.left + rulerWidth;
    }

    /**
     * Returns the vertical ruler.
     *
     * @return the vertical ruler
     */
    public TSRulerImpl getRuler() {
        return vertRuler;
    }

    /**
     * Paints the ruler on the specified graphics object.
     *
     * @param g2d the graphics object for rendering
     */
    public void paint(Graphics2D g2d, long intervalBeginTime) {
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, width, height);

        g2d.setColor(Color.BLACK);

        //g2d.drawRect(margin.left + rulerWidth, margin.top, 
        //	width - margin.left - margin.right - rulerWidth, height - margin.top - margin.bottom);
        g2d.drawRect(trackRect.x, trackRect.y, trackRect.width, trackRect.height);

        g2d.translate(margin.left, margin.top);
        vertRuler.paint(g2d);
        g2d.setColor(Constants.DEFAULTBACKGROUNDCOLOR);
        tickYPos = vertRuler.getTickYPositions();

        for (int i = 0; i < tickYPos.length; i++) {
            g2d.drawLine(trackRect.x, tickYPos[i],
                trackRect.x + trackRect.width, tickYPos[i]);
        }

        g2d.translate(trackRect.x, 0);

        g2d.setClip(0, 0, trackRect.width, trackRect.height);

        for (int i = 0; i < tracks.size(); i++) {
            paintTrack(g2d, (AbstractTSTrack) tracks.get(i), intervalBeginTime,
                trackRect.width, trackRect.height);
        }

        g2d.setClip(null);
        g2d.translate(-margin.left - trackRect.x, -margin.top);
    }

    /**
     * Renders track data to the specified Graphics context. Delegates rendering to one
     * of the data format specific methods.
     *
     * @param g2d the Graphics context
     * @param track the time series track to render.
     * @param beginTime interval begin time
     * @param the width of the paint area
     * @param the height of the paint area
     */
    public void paintTrack(Graphics2D g2d, AbstractTSTrack track,
        long beginTime, int width, int height) {
        if ((g2d == null) || (track == null)) {
            return;
        }

        switch (track.getType()) {
        case TimeSeriesTrack.VALUES_INT_ARRAY:

            //paintIntArrayTrack(g2d, track, beginTime, width, height);
            break;

        case TimeSeriesTrack.VALUES_FLOAT_ARRAY:
            paintFloatArrayTrack(g2d, track, beginTime, width, height);

            break;

        case TimeSeriesTrack.VALUES_DOUBLE_ARRAY:

            //paintDoubleArrayTrack(g2d, track, beginTime, width, height);
            break;

        case TimeSeriesTrack.TIME_VALUES_MAP:
            //paintMappedTrack(g2d, track, beginTime, width, height);	
        }
    }

    /**
     * Renders a track that stores its data in a float array.
     * Null checking has been done in paintTrack().
     *
     * @param g2d the Graphics context
     * @param track the time series track to render.
     * @param beginTime interval begin time
     * @param the width of the paint area
     * @param the height of the paint area
     * @see #paintTrack(Graphics2D, AbstractTSTrack, long, int, int)
     */
    private void paintFloatArrayTrack(Graphics2D g2d, AbstractTSTrack track,
        long beginTime, int width, int height) {
        g2d.setColor(track.getColor());

        float[] data = (float[]) track.getData();
        float[] range = vertRuler.getRange();
        float scaleUnit = height / (range[1] - range[0]);
        long endTime = beginTime + (long) (width * msPerPixel);
        int xShift = (int) (beginTime / msPerPixel) +
            (int) (track.timeOffset / msPerPixel);
        int beginIndex = track.getIndexForTime(beginTime);
        int endIndex = track.getIndexForTime(endTime);
        float samplesPerPixel = msPerPixel * (track.getSampleRate() / 1000);

        int x1 = 0;
        int x2 = -1;
        int y1 = 0;
        int y2 = 0;

        if (samplesPerPixel > 1) {
            // per pixel calculation
            int index1 = beginIndex;
            int index2 = index1;

            for (int i = 0; i <= width; i++) {
                float val = 0;
                index1 = track.getIndexForTime(beginTime +
                        (int) (i * msPerPixel));
                index2 = track.getIndexForTime(beginTime +
                        (int) ((i + 1) * msPerPixel));

                if ((index1 < 0) || (index2 < 0) || (index1 == index2)) {
                    continue;
                }

                if (index2 >= data.length) {
                    break;
                }

                for (int j = index1; j < index2; j++) {
                    val += data[j];
                }

                // calculate average
                val /= (index2 - index1);

                if (x2 == -1) {
                    // first point
                    x2 = i;
                    y2 = (int) (scaleUnit * (range[1] - val));
                } else {
                    x1 = x2;
                    y1 = y2;
                    x2 = i;
                    y2 = (int) (scaleUnit * (range[1] - val));
                    g2d.drawLine(x1, y1, x2, y2);
                }
            }
        } else {
            // per sample calculation
            float pixelPerSample = 1 / samplesPerPixel;

            for (int i = beginIndex; (i <= endIndex) && (i < data.length);
                    i++) {
                if (i < 0) {
                    continue;
                }

                float v = data[i];

                if (x2 == -1) {
                    // first point
                    x2 = (int) (i * pixelPerSample) - xShift;
                    y2 = (int) (scaleUnit * (range[1] - v));
                } else {
                    x1 = x2;
                    y1 = y2;
                    x2 = (int) (i * pixelPerSample) - xShift;
                    y2 = (int) (scaleUnit * (range[1] - v));
                    g2d.drawLine(x1, y1, x2, y2);
                }
            }
        }
    }

    /**
     * Sets the vertical zoom level.
     *
     * @param vertZoom the vertical zoom level
     */
    public void setVerticalZoom(float vertZoom) {
        zoomLevel = vertZoom;
    }

    /**
     * Returns the current vertical zoomlevel.
     *
     * @return the current vertical zoomlevel
     */
    public float getVerticalZoom() {
        return zoomLevel;
    }

    /**
     * Adds a track to the panel.
     *
     * @param track the track to add to the panel
     *
     * @see mpi.eudico.client.annotator.timeseries.TSTrackPanel#addTrack(mpi.eudico.client.annotator.timeseries.ContinuousRateTSTrack)
     */
    public void addTrack(AbstractTSTrack track) {
        tracks.add(track);
    }

    /**
     * Removes a track from the panel.
     *
     * @param track the track to remove from the panel
     *
     * @return true if the track was in the list of tracks and has been
     *         removed, false otherwise
     *
     * @see mpi.eudico.client.annotator.timeseries.TSTrackPanel#removeTrack(mpi.eudico.client.annotator.timeseries.AbstractTSTrack)
     */
    public boolean removeTrack(AbstractTSTrack track) {
        return tracks.remove(track);
    }

    /**
     * Removes a track identified by trackID from the panel.
     *
     * @param trackID the name or id of the track to remove from the panel
     *
     * @return true if the track was in the list of tracks and has been
     *         removed, false otherwise
     *
     * @see mpi.eudico.client.annotator.timeseries.TSTrackPanel#removeTrack(java.lang.String)
     */
    public boolean removeTrack(String trackID) {
        if (trackID == null) {
            return false;
        }

        AbstractTSTrack track = getTrack(trackID);

        if (track != null) {
            return tracks.remove(track);
        }

        return false;
    }

    /**
     * Returns the track identified by trackID.
     *
     * @param trackID the name or id of the track
     *
     * @return the track, or null if not present in the list
     *
     * @see mpi.eudico.client.annotator.timeseries.TSTrackPanel#getTrack(java.lang.String)
     */
    public AbstractTSTrack getTrack(String trackID) {
        AbstractTSTrack track = null;
        AbstractTSTrack tr = null;

        for (int i = 0; i < tracks.size(); i++) {
            tr = (AbstractTSTrack) tracks.get(i);

            if (tr.getName().equals(trackID)) {
                track = tr;

                break;
            }
        }

        return track;
    }

    /**
     * Returns the list of tracks in this panel.
     *
     * @return the list of tracks
     *
     * @see mpi.eudico.client.annotator.timeseries.TSTrackPanel#getTracks()
     */
    public List getTracks() {
        return tracks;
    }

    /**
     * Sets the display height for this panel.
     *
     * @param height the new height
     *
     * @see mpi.eudico.client.annotator.timeseries.TSTrackPanel#setHeight(int)
     */
    public void setHeight(int height) {
        this.height = height;
        vertRuler.setHeight(height - margin.top - margin.bottom);
        trackRect.height = height - margin.top - margin.bottom;
    }

    /**
     * @see mpi.eudico.client.annotator.timeseries.TSTrackPanel#getHeight()
     */
    public int getHeight() {
        return height;
    }

    /**
     * @see mpi.eudico.client.annotator.timeseries.TSTrackPanel#setWidth(int)
     */
    public void setWidth(int width) {
        this.width = width;
        trackRect.width = width - rulerWidth - margin.left - margin.right;
    }

    /**
     * @see mpi.eudico.client.annotator.timeseries.TSTrackPanel#getWidth()
     */
    public int getWidth() {
        return width;
    }

    /**
     * @see mpi.eudico.client.annotator.timeseries.TSTrackPanel#setMargin(int[])
     */
    public void setMargin(Insets margin) {
        this.margin = margin;
        vertRuler.setHeight(height - margin.top - margin.bottom);
        trackRect.x = margin.left;
        trackRect.y = margin.top;
        trackRect.width = width - rulerWidth - margin.left - margin.right;
        trackRect.height = height - margin.top - margin.bottom;
    }

    /**
     * @see mpi.eudico.client.annotator.timeseries.TSTrackPanel#getMargin()
     */
    public Insets getMargin() {
        return margin;
    }

    /**
     * Returns the current resolution, or number of milliseconds per pixel.
     * @return number of milliseconds per pixel
     */
    public float getMsPerPixel() {
        return msPerPixel;
    }

    /**
     * Sets the resolution or number of milliseconds per pixel.
     * @param msPerPixel the number of ms that each pixel represents
     */
    public void setMsPerPixel(float msPerPixel) {
        this.msPerPixel = msPerPixel;
    }
}
