/*
 * Worldvisions Weaver Software:
 *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
 * 
 * WvRateAdjust is a WvEncoder that makes sure data comes out of it at a
 * given average rate.
 * 
 * See wvrateadjust.h.
 */
#include "wvrateadjust.h"

WvRateAdjust::WvRateAdjust(int _sampsize, int _irate_base, int _orate)
#if 0
    : log("RateAdj", WvLog::Debug5)
#endif
{
    orate_n = _orate;
    orate_d = 1;
    match_rate = NULL;
    
    init(_sampsize, _irate_base);
}


WvRateAdjust::WvRateAdjust(int _sampsize, int _irate_base,
			   WvRateAdjust *_match_rate)
#if 0
    : log("RateAdj", WvLog::Debug5)
#endif
{
    match_rate = _match_rate;
    assert(match_rate);
    
    orate_n = match_rate->irate_n;
    orate_d = match_rate->irate_d;
    
    init(_sampsize, _irate_base);
}


void WvRateAdjust::init(int _sampsize, int _irate_base)
{
    sampsize = _sampsize;
    irate_n = _irate_base * 10;
    irate_d = 10;
    epoch = wvtime();
    epoch.tv_sec--;
    bucket = 0;
}


// we always use all input samples and produce an appropriate number of
// output samples.
bool WvRateAdjust::_encode(WvBuf &inbuf, WvBuf &outbuf, bool flush)
{
    if (!inbuf.used()) return true;
    assert((inbuf.used() % sampsize) == 0); // can't deal with partial samples
    
    WvTime now = wvtime();
    unsigned isamps = inbuf.used() / sampsize;
    
    // match our output rate to another stream's input rate, if requested
    if (match_rate)
    {
	orate_n = match_rate->irate_n;
	orate_d = match_rate->irate_d;
    }
    
    // adjust the input rate estimate
    if (!epoch.tv_sec)
	epoch = now;
    irate_n += isamps * 10;
    irate_d = msecdiff(wvtime(), epoch) / 100;
    if (!irate_d)
	irate_d = 1;

#if 0
    log("irate=%s (%s/%s), orate=%s (%s/%s), bucket=%s\n",
	getirate(), irate_n, irate_d, getorate(), orate_n, orate_d,
	bucket);
#endif

    // reduce the rate estimate if it's getting out of control FIXME:
    // this method is (almost) unbearably cheesy because it's very
    // "blocky" - it doesn't happen every time, so it'll cause sudden
    // jumps from one value to the next.  Hopefully not a big deal,
    // since the input rate is supposed to be constant anyway.  The
    // hardcoded constants are also rather weird.
    if (irate_d > 100) // ten seconds
    {
	epoch.tv_sec++; // time now starts one second later
	irate_n = irate_n * (irate_d - 10)/irate_d;
	irate_d -= 10;

#if 0
	log("  JUMP!  new irate=%s (%s/%s)\n", getirate(), irate_n, irate_d);
#endif
    }
	
    int plus = orate_n * irate_d, minus = irate_n * orate_d;
    //log("plus=%s, minus=%s, ", plus, minus);

    unsigned omax = isamps + isamps/2;
    //log("isamps=%s, omax=%s\n", isamps, omax);
    
    const unsigned char *iptr = inbuf.get(isamps * sampsize);
    unsigned char *ostart, *optr;
    
    ostart = optr = outbuf.alloc(omax * sampsize);

    // copy the buffers using the "Bresenham line-drawing" algorithm.
    for (unsigned s = 0; s < isamps; s++, iptr += sampsize)
    {
	bucket += plus;
	//log("s=%s, bucket=%s (+%s, -%s)\n", s, bucket, plus, minus);
	
	while (bucket >= minus)
	{
	    // allocate more buffer space if needed
	    if ((unsigned)(optr - ostart) >= omax * sampsize)
		ostart = optr = outbuf.alloc(omax * sampsize);
	    
	    for (int i = 0; i < sampsize; i++)
		optr[i] = iptr[i];
	    optr += sampsize;
	    bucket -= minus;
	}
    }
    
    unsigned un = omax*sampsize - (optr - ostart);
    //log("unalloc %s/%s (%s)\n", un, omax*sampsize, optr-ostart);
    outbuf.unalloc(un);
    
    return true;
}


