/*
 * 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.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.File;
import java.io.IOException;

/**
 * Down sample
 */
public class Downsampler extends Resampler {
    private long totalTime;
    private int resampleCount;

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

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

	if (inSampleRate < outSampleRate) {
            throw new IOException("Downsampler inSampleRate "
                + inSampleRate + " < outSampleRate " + outSampleRate);
	}
	if (Logger.logLevel >= Logger.LOG_MOREINFO) {
	    Logger.println("New DownSampler:  from " 
	        + inSampleRate + "/" + inChannels + " to " 
	        + outSampleRate + "/" + outChannels);
	}
    }

    public void reset() {
    }

    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();

	int[] outSamples = reChannel(inSamples);

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

	outSamples = lowPassFilter.lpf(outSamples);

	outSamples = downsample(outSamples);

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

	return outSamples;
    }

    public int[] downsample(int[] inSamples) {
	int nFrames = inSamples.length / outChannels;

	int sampleTime = nFrames * 1000 / inSampleRate;

	if (sampleTime == 0) {
	    sampleTime = 1;
	}

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

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

	if (outLength == 0 || Logger.logLevel == -9) {
	    Logger.println("downsample:  inLength " + inSamples.length
		+ " nFrames " + nFrames
		+ " sampleTime " + sampleTime + " outLength " + outLength);
	}

	int[] outSamples = new int[outLength];

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

	int ix;

	double i = 0;

	int outIx = 0;

	if (Logger.logLevel == -9) {
	    Logger.println("downsample frameIncr " + frameIncr
		+ " nFrames " + nFrames + " inLength " + inSamples.length
		+ " outLength " + outLength);

	    Logger.println("inSamples");
	}

	/*
	 * Linear interpolation between the two closest samples.
	 */
	while (true) {
	    ix = (int)i * outChannels;

	    if (ix >= inSamples.length - outChannels) {
		// Don't we need to continue until outIx >= outLength?
		if (Logger.logLevel == -9) {
		    Logger.println("Out of here!  ix " + ix + " outIx " + outIx);
		}
		break;
	    }

	    int s1 = inSamples[ix];
	    int s2 = inSamples[ix + outChannels];

	    if (Logger.logLevel == -9) {
		Logger.println("s1 " + s1 + " s2 " + s2 + " int i " + (int)i
		    + " ix " + ix  + " outIx " + outIx);
	    }

	    outSamples[outIx] = (int) ((s1 + ((s2 - s1) * (i - (int)i))));

	    outIx++;

	    if (outChannels == 2) {
		ix++;

	        s1 = inSamples[ix];
		s2 = inSamples[ix + outChannels];

	        outSamples[outIx] = (int) ((s1 + ((s2 - s1) * (i - (int)i))));

	        if (Logger.logLevel == -9) {
		    Logger.println("+s1 " + s1 + " s2 " + s2 + " int i " + (int)i
		        + " ix " + ix  + " outIx " + outIx);
	        }

		outIx++;
	    }

	    if (outIx >= outLength) {
		if (Logger.logLevel == -9) {
		    Logger.println("Out of here!  outIX " + outIx 
			+ " ix " + ix);
		}
		break;
	    }

	    i += frameIncr;
	}

	if (Logger.logLevel == -9) {
	    Logger.logLevel = 3;

	    Logger.println("downsample in len " + inSamples.length);
	    Logger.println("downsample out len " + outSamples.length);
	}

	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 downsample time from "
	    + inSampleRate + "/" + inChannels + " to " + outSampleRate + "/"
	    + outChannels);

	lowPassFilter.printStatistics();
    }

}
