/*
 * Copyright 2007 Sun Microsystems, Inc.
 *
 * This file is part of jVoiceBridge.
 *
 * jVoiceBridge is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License version 2 as 
 * published by the Free Software Foundation and distributed hereunder 
 * to you.
 *
 * jVoiceBridge 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, see <http://www.gnu.org/licenses/>.
 *
 * Sun designates this particular file as subject to the "Classpath"
 * exception as provided by Sun in the License file that accompanied this 
 * code. 
 */

package com.sun.voip;

import java.io.IOException;

/**
 * Upsampler
 */
public class Upsampler extends Resampler {
    private long totalTime;
    private int resampleCount;

    private int[] lastSample;		// for upsampling

    /*
     * XXX We only support big endian 16 bit samples!
     */
    public Upsampler(String id, int inSampleRate, int inChannels,
	    int outSampleRate, int outChannels) throws IOException {

	super(id, inSampleRate, inChannels, outSampleRate, outChannels);

	if (inSampleRate > outSampleRate) {
	    throw new IOException("Upsampler inSampleRate "
		+ inSampleRate + " > outSampleRate " + outSampleRate);
	}

	if (Logger.logLevel >= Logger.LOG_MOREINFO) {
	    Logger.println("New Upsampler:  from " 
	        + inSampleRate + "/" + inChannels + " to " 
	        + outSampleRate + "/" + outChannels);
	}

	reset();
    }

    public void reset() {
	lastSample = new int[outChannels];
    }

    public byte[] resample(byte[] inSamples, int offset, int length) 
	    throws IOException {

	length = length & ~1;	// round down

	int[] ints = new int[length / 2];

	AudioConversion.bytesToInts(inSamples, offset, length, ints);

	ints = resample(ints);

	byte[] bytes = new byte[ints.length * 2];

	AudioConversion.intsToBytes(ints, bytes, offset);

	return bytes;
    }

    public int[] resample(int[] inSamples) throws IOException {
	if (inSampleRate == outSampleRate && inChannels == outChannels) {
	    return inSamples;
	}

	resampleCount++;

	long start = CurrentTime.getTime();

	/*
 	 * Convert mono to multi-channel or vice versa as needed.
	 *
	 * Upsampling is done by interpolating between data points
	 * and producing the right number of output samples.
	 */
	int[] outSamples = reChannel(inSamples);

	if (inSampleRate == outSampleRate) {
	    return outSamples;				// no need to resample
	}

	outSamples = upsample(outSamples);

	outSamples = lowPassFilter.lpf(outSamples);

	totalTime += (CurrentTime.getTime() - start);

	return outSamples;
    }

    private int[] upsample(int[] inSamples) {
        /*
         * Calculate the number of inSamples needed to produce an outSample.
         * Round to the nearest integer.
	 * XXX The number of input samples must be divide into the
	 * input sample rate or else the outLength will be too small!
         */
        int nSamples = inSamples.length / outChannels;

        double sampleTime = (nSamples * 1000.0D) / inSampleRate;

        int outLength = (int)(Math.round(
            (sampleTime * outSampleRate * outChannels / 1000)));

	//Logger.println("outLength " + outLength);

	if ((outLength & 1) != 0) {
	    outLength++;
	}

        int[] outSamples = new int[outLength];

	double frameIncr = (double)inSampleRate / (double)(outSampleRate);

	int outIx = 0;

	int[] last = new int[outChannels];

	last[0] = inSamples[inSamples.length - outChannels];

	if (outChannels == 2) {
	    last[1] = inSamples[inSamples.length - outChannels + 1];
	}

	int ix = 0;
	double i = 0;

	/*
	 * Linear interpolation between each two samples
	 */
	while (true) {
	    int intI = (int)i;

	    ix = intI * outChannels;

	    if (ix >= inSamples.length || outIx + outChannels > outLength ) {
		break;
	    }

	    int s1;

	    if (ix == 0) {
		s1 = lastSample[0];
	    } else {
	        s1 = inSamples[ix - outChannels];
	    } 

	    int s2 = inSamples[ix];

	    int newSample = (int)(s1 + ((s2 - s1) * (i - intI)));

	    outSamples[outIx] = (int) newSample;

	    outIx++;

	    if (outChannels == 2) {
		if (ix == 0) {
		    s1 = lastSample[1];
		} else {
	            s1 = inSamples[ix - outChannels + 1];
		}
		
	        s2 = inSamples[ix + 1];

		newSample = (int)(s1 + ((s2 - s1) * (i - intI)));

	        outSamples[outIx] = (int) newSample;

	        outIx++;
	    }

	    if (outIx >= outLength) {
		break;
	    }

	    i += frameIncr;
	}

	lastSample = last;
	return outSamples;
    }

    public void printStatistics() {
	if (resampleCount == 0) {
	    return;
	}

	double avg = (double)totalTime / resampleCount;

	long timeUnitsPerSecond = CurrentTime.getTimeUnitsPerSecond();

	avg = (avg / timeUnitsPerSecond) * 1000;

	String s = "";

	if (id != null) {
            s += "Call " + id + ":  ";
	}

	Logger.writeFile(s
	    + avg + "ms avg upsample time from "
	    + inSampleRate + "/" + inChannels + " to " + outSampleRate + "/"
	    + outChannels);

	lowPassFilter.printStatistics();
    }

}
