/*  akodePlayObject

    Copyright (C) 2003-2005 Allan Sandfeld Jensen <kde@carewolf.com>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

//#define AKODEARTS_SINGLETHREADED
//#define AKODEARTS_SRCRESAMPLING
#define AKODEARTS_FRAMEBUFFER 32
// The instream-buffer must be smaller than 64kbytes (1<<16)
#define AKODEARTS_INSTREAMBUFFER (1<<14)

#include <math.h>

#include <soundserver.h>
#include <audiosubsys.h>
#include <debug.h>

#include "config.h"

#include <akode/audioframe.h>
#include <akode/localfile.h>
#include <akode/mmapfile.h>
#include <akode/decoder.h>
#include <akode/resampler.h>
#include <akode/fast_resampler.h>
#include <akode/wav_decoder.h>

#include "arts_inputstream.h"

#ifndef AKODEARTS_SINGLETHREADED
 #include <akode/buffered_decoder.h>
#endif

#ifdef AKODEARTS_SRCRESAMPLING
#define AKODEARTS_RESAMPLER "src"
#else
#define AKODEARTS_RESAMPLER "fast"
#endif


#include "akodePlayObject_impl.h"

using namespace Arts;
using namespace aKode;

akodePlayObject_impl::akodePlayObject_impl(const string &plugin)
  : source(0)
  , frameDecoder(0)
  , decoder(0)
  , bufferedDecoder(0)
  , resampler(0)
  , buffer(0)
  , inBuffer(0)
  , buf_pos(0)
  , mState(posIdle)
  , mSpeed(1.0)
  , m_packetQueue(0)
  , m_bytebuffer(0)
  , m_fading(false)
  , decoderPlugin(plugin)
  , resamplerPlugin(AKODEARTS_RESAMPLER)
{
    m_packetQueue = new queue<DataPacket<mcopbyte>*>;
    if(!resamplerPlugin.isLoaded())
        resamplerPlugin.load("fast");
}

akodePlayObject_impl::~akodePlayObject_impl()
{
    delete m_packetQueue;

    unload();
}

bool akodePlayObject_impl::loadPlugin(const string &plugin)
{
    return decoderPlugin.load(plugin);
}

bool akodePlayObject_impl::streamMedia(Arts::InputStream inputstream)
{
    arts_debug("akode: opening input-stream");
    m_bytebuffer = new aKode::ByteBuffer(AKODEARTS_INSTREAMBUFFER);
    instream = inputstream;

    Arts::StreamPlayObject self = Arts::StreamPlayObject::_from_base(_copy());
    connect(instream, "outdata", self, "indata");

    source = new Arts_InputStream(instream, m_bytebuffer);
    return loadSource();
}

bool akodePlayObject_impl::loadMedia(const string &filename)
{
    arts_debug("akode: opening %s", filename.c_str());
    source = new MMapFile(filename.c_str());
    if (!source->openRO()) {
        delete source;
        source = new LocalFile(filename.c_str());
        if (!source->openRO()) {
            delete source;
            source = 0;
            return false;
        }
    }
    source->close();
    return loadSource();
}

bool akodePlayObject_impl::loadSource()
{
    if (!decoderPlugin.isLoaded()) {
        return false;
    }
    frameDecoder = decoderPlugin.openDecoder(source);

    if (!frameDecoder) {
        delete source;
        source = 0;
        arts_warning("akode: Could not open frame-decoder");
        return false;
    }

#ifndef AKODEARTS_SINGLETHREADED
    bufferedDecoder = new BufferedDecoder();
    bufferedDecoder->setBufferSize(AKODEARTS_FRAMEBUFFER);
    bufferedDecoder->openDecoder(frameDecoder);
    decoder = bufferedDecoder;
#else
    decoder = frameDecoder;
#endif

    return true;
}

string akodePlayObject_impl::description()
{
    return "akodePlayObject";
}

Arts::InputStream akodePlayObject_impl::inputStream() {
    return instream;
}

string akodePlayObject_impl::mediaName()
{
    if (source)
        return source->filename;
    else
        return string();
}

poCapabilities akodePlayObject_impl::capabilities()
{
    return (poCapabilities)(capPause | capSeek);
}

poState akodePlayObject_impl::state()
{
    return mState;
}

void akodePlayObject_impl::play()
{
    arts_debug("akode: play");

    if (!decoder) {
        arts_warning("akode: No media loaded");
	return;
    }

    if (mState == posIdle) {
        mState = posPlaying;

        if (!inBuffer) inBuffer = new AudioFrame;
	if (!buffer) buffer = inBuffer;


	buf_pos = 0;
    } else
        mState = posPlaying;
}

void akodePlayObject_impl::pause()
{
    arts_debug("akode: pause");
    mState = posPaused;
}

void akodePlayObject_impl::halt()
{
    arts_debug("akode: halt");
    if (mState == posIdle) return;
    mState = posIdle;
    unload();
}

void akodePlayObject_impl::unload()
{
    arts_debug("akode: unload");
    if (m_bytebuffer) m_bytebuffer->release();
#ifndef AKODEARTS_SINGLETHREADED
    if (bufferedDecoder) {
        bufferedDecoder->stop();
        bufferedDecoder->closeDecoder();
        delete bufferedDecoder;
        bufferedDecoder = 0;
    }
#endif
    delete frameDecoder;
    frameDecoder = 0;
    decoder = 0;
    if (buffer != inBuffer)
        delete inBuffer;
    delete buffer;
    inBuffer = buffer = 0;
    buf_pos = 0;

    delete resampler;
    resampler = 0;
    delete source;
    source = 0;
#ifndef AKODEARTS_SINGLETHREADED
    delete m_bytebuffer;
    m_bytebuffer = 0;
#endif
}

poTime akodePlayObject_impl::currentTime()
{
    poTime time;

    long pos;
    if (decoder) {
        pos = decoder->position();            // decoder time
        if (pos < 0 ) pos = 0;
        else
        if (samplingRate > 0 && buffer)
        {
            float lpos = (float)(buf_pos-buffer->length) / (float)samplingRate;  // local time
            pos += (long)(lpos*1000.0);
        }
    }
    else
        pos = 0;

    time.seconds = pos / 1000 ;
    time.ms = pos % 1000;

    return time;
}

poTime akodePlayObject_impl::overallTime()
{
    poTime time;

    long len;
    if (decoder) {
        len = decoder->length();
        if (len < 0 ) len = 0;
    }
    else
        len = 0;

    time.seconds = len / 1000;
    time.ms = len % 1000;

    return time;
}

void akodePlayObject_impl::seek(const poTime &time)
{
    arts_debug("akode: seek");
    if (!decoder) {
        arts_warning("akode: No media loaded");
	return;
    }
    long akode_pos = time.seconds*1000+time.ms;

    if (decoder->seek(akode_pos) && buffer) {
        buffer->length = 0;  // force re-read
        buf_pos = 0;
    }
}

bool akodePlayObject_impl::readFrame()
{
    arts_debug("akode: readFrame");

    if (!inBuffer || !decoder) return false;
    if (m_bytebuffer) processQueue();
    if(!decoder->readFrame(inBuffer)) {
        if (decoder->eof()) {
            arts_debug("akode: eof");
            halt();
        } else
        if (decoder->error()) {
            arts_debug("akode: error");
            halt();
        } else
            buffer->length=0;
        return false;
    }

    // Invalid frame from broken plugin
    if (inBuffer->sample_rate == 0) return false;

    if (samplingRate != inBuffer->sample_rate || mSpeed != 1.0) {
        //arts_debug("akode: resampling to %d/%d", inBuffer->sample_rate, samplingRate);
        if ( !buffer || buffer==inBuffer ) buffer = new AudioFrame;
        if ( !resampler)
            resampler = resamplerPlugin.openResampler();

        resampler->setSampleRate(samplingRate);
        resampler->setSpeed(mSpeed);

        resampler->doFrame(inBuffer, buffer);
    } else {
        if ( buffer !=inBuffer) delete buffer;
        buffer = inBuffer;
    }

    buf_pos = 0;
    return true;
}

bool akodePlayObject_impl::eof()
{
    if (!decoder || !buffer) return true;
    else
        return buf_pos>=buffer->length && decoder->eof();
}

// GCC's lack of good template support means this is easyist done using DEFINE
#define SEND_BLOCK(T)  \
    T* data = (T*)buffer->data[0]; \
    j = i; bp = buf_pos; \
    while (bp < buffer->length && j<count) { \
    	left[j] = data[bp]*scale; \
        j++; bp++; \
    } \
    if (buffer->channels > 1) \
    	data = (T*)buffer->data[1]; \
    j = i; bp = buf_pos; \
    while (bp < buffer->length && j<count) { \
    	right[j] = data[bp]*scale; \
    	j++; bp++; \
    }


void akodePlayObject_impl::calculateBlock(unsigned long cnt)
{
    long count = (long)cnt;
//    arts_debug("akode: calculateBlock");
    long i = 0, j, bp;

    if (!decoder) {
        arts_warning("akode: No media loaded");
	goto fill_out;
    }

    if (!buffer)
        // Not playing yet
        goto fill_out;

    while ((mState == posPlaying || m_fading) && i<count) {
	if (buf_pos >= buffer->length) {
            buf_pos = 0;
	    if (!readFrame()) {
                break;
	    }
	}
        if (buffer->channels > 2 || buffer->sample_width > 24 || buffer->sample_width == 0) {
            arts_warning("akode: Incompatible media");
	    halt();
	    break;
	}

        signed char width = buffer->sample_width;
        float scale = (float)(1<<(width-1));
	if (width >= 0) {
            scale = 1/scale;
            if (width <= 8) {
	       SEND_BLOCK(int8_t);
	       i=j;
               buf_pos=bp;
            }
	    else if (width <= 16) {
	       SEND_BLOCK(int16_t);
	       i=j;
               buf_pos=bp;
	   }
           else {
	       SEND_BLOCK(int32_t);
	       i=j;
               buf_pos=bp;
	   }
        }
        else {
            scale = 1.0;
            SEND_BLOCK(float);
            i=j;
            buf_pos=bp;
        }
    }

    // fill-out the rest with silence
fill_out:
    for (; i < count; i++) {
    	left[i] = right[i] = 0;
    }

}

void akodePlayObject_impl::streamInit()
{
    arts_debug("akode: streamInit");
}

void akodePlayObject_impl::streamStart()
{
    arts_debug("akode: streamStart");
#ifndef AKODEARTS_SINGLETHREADED
    bufferedDecoder->start();
#endif
}

void akodePlayObject_impl::streamEnd()
{
    arts_debug("akode: streamEnd");
    mState = posIdle;
    if (decoder) unload();
}

void akodePlayObject_impl::speed(float newValue)
{
    mSpeed = newValue;
}

float akodePlayObject_impl::speed()
{
    return mSpeed;
}

void akodePlayObject_impl::process_indata(DataPacket<mcopbyte> *inpacket) {
    arts_debug("akode: process_indata");
    m_packetQueue->push(inpacket);
    if (!m_bytebuffer) return;
    processQueue();
}

void akodePlayObject_impl::processQueue()
{
    while (!m_packetQueue->empty()) {
        long freespace = m_bytebuffer->space();

        DataPacket<mcopbyte> *inpacket = m_packetQueue->front();
        if (!inpacket) return;

        if (freespace >= inpacket->size) {
            if (m_bytebuffer->write((char*)inpacket->contents, inpacket->size, false)) {
                m_packetQueue->pop();
                inpacket->processed();
            }
        } else
            return;
    }
    if (instream.eof()) m_bytebuffer->close();
}


REGISTER_IMPLEMENTATION(akodePlayObject_impl);