/*  aKode: Player

    Copyright (C) 2004-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 Steet, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include <cstring>

#include <pthread.h>
#include <semaphore.h>
#include <assert.h>

#include "audioframe.h"
#include "audiobuffer.h"
#include "decoder.h"
#include "buffered_decoder.h"
#include "mmapfile.h"
#include "localfile.h"
#include "volumefilter.h"

#include "sink.h"
#include "converter.h"
#include "resampler.h"
#include "magic.h"

#include "player.h"

#ifndef NDEBUG
#include <iostream>
#define AKODE_DEBUG(x) {std::cerr << "akode: " << x << "\n";}
#else
#define AKODE_DEBUG(x) { }
#endif

namespace aKode {

struct Player::private_data
{
    private_data() : src(0)
                   , frame_decoder(0)
                   , resampler(0)
                   , converter(0)
                   , volume_filter(0)
                   , sink(0)
                   , manager(0)
                   , monitor(0)
                   , decoder_plugin(0)
                   , resampler_plugin("fast")
                   , sample_rate(0)
                   , state(Closed)
                   , my_file(false)
                   , my_sink(false)
                   , start_pos(0)
                   , halt(false)
                   , pause(false)
                   , detached(false)
                   , running(false)
                   {};

    File *src;

    Decoder *frame_decoder;
    BufferedDecoder buffered_decoder;
    Resampler *resampler;
    Converter *converter;
    // Volatile because it can be created and destroyed during playback
    VolumeFilter* volatile volume_filter;
    Sink *sink;
    Player::Manager *manager;
    Player::Monitor *monitor;

    const char* decoder_plugin;
    const char* resampler_plugin;

    SinkPluginHandler sink_handler;
    DecoderPluginHandler decoder_handler;
    ResamplerPluginHandler resampler_handler;

    unsigned int sample_rate;
    State state;
    bool my_file;
    bool my_sink;
    int start_pos;

    volatile bool halt;
    volatile bool pause;
    volatile bool detached;
    bool running;
    pthread_t player_thread;
    sem_t pause_sem;

    void setState(Player::State s) {
        state = s;
        if (manager) manager->stateChangeEvent(s);
    }
    // Called for detached players
    void cleanup() {
        buffered_decoder.stop();
        buffered_decoder.closeDecoder();

        delete frame_decoder;
        if (my_file)
            delete src;

        frame_decoder = 0;
        src = 0;
        decoder_handler.unload();

        delete resampler;
        delete converter;
        resampler = 0;
        converter = 0;

        delete this;
    }
};

// The player-thread. It is controlled through the two variables
// halt and seek_pos in d
static void* run_player(void* arg) {
    Player::private_data *d = (Player::private_data*)arg;

    AudioFrame frame;
    AudioFrame re_frame;
    AudioFrame c_frame;
    bool no_error = true;

    while(true) {
        if (d->pause) {
            d->sink->pause();
            sem_wait(&d->pause_sem);
            d->sink->resume();
        }
        if (d->halt) break;

        no_error = d->buffered_decoder.readFrame(&frame);

        if (!no_error) {
            if (d->buffered_decoder.eof())
                goto eof;
            else
            if (d->buffered_decoder.error())
                goto error;
            else
                AKODE_DEBUG("Blip?");
        }
        else {
            AudioFrame* out_frame = &frame;
            if (d->resampler) {
                d->resampler->doFrame(out_frame, &re_frame);
                out_frame = &re_frame;
            }

            if (d->converter) {
                d->converter->doFrame(out_frame, &c_frame);
                out_frame = &c_frame;
            }

            if (d->volume_filter)
                d->volume_filter->doFrame(out_frame);

            no_error = d->sink->writeFrame(out_frame);

            if (d->monitor)
                d->monitor->writeFrame(out_frame);

            if (!no_error) {
                // ### Check type of error
                goto error;
            }
        }
    }

// Stoped by Player::stop()
//     d->buffered_decoder->stop();
    assert(!d->detached);
    return (void*)0;

error:
    if (d->detached) d->cleanup();
    else {
        d->buffered_decoder.stop();
        if (d->manager)
            d->manager->errorEvent();
    }
    return (void*)0;

eof:
    if (d->detached) d->cleanup();
    else {
        d->buffered_decoder.stop();
        if (d->manager)
            d->manager->eofEvent();
    }
    return (void*)0;
}

Player::Player() {
    d = new private_data;
    sem_init(&d->pause_sem,0,0);
}

Player::~Player() {
    close();
    sem_destroy(&d->pause_sem);
    delete d;
}

bool Player::open(const char* sinkname) {
    if (state() != Closed)
        close();

    assert(state() == Closed);

    d->sink_handler.load(sinkname);
    if (!d->sink_handler.isLoaded()) {
        AKODE_DEBUG("Could not load " << sinkname << "-sink");
        return false;
    }
    d->sink = d->sink_handler.openSink();
    if (!d->sink) {
        AKODE_DEBUG("Could not create " << sinkname << "-sink");
        return false;
    }
    if (!d->sink->open()) {
        AKODE_DEBUG("Could not open " << sinkname << "-sink");
        delete d->sink;
        d->sink = 0;
        return false;
    }
    d->my_sink = true;
    setState(Open);
    return true;
}

bool Player::open(Sink *sink) {
    if (state() != Closed)
        close();

    assert(state() == Closed);

    d->sink = sink;
    if (!d->sink->open()) {
        AKODE_DEBUG("Could not open sink");
        d->sink = 0;
        return false;
    }
    d->my_sink = false;
    setState(Open);
    return true;
}

void Player::close() {
    if (state() == Closed) return;
    if (state() != Open)
        unload();

    assert(state() == Open);

    delete d->volume_filter;
    d->volume_filter = 0;

    if (d->my_sink) delete d->sink;
    d->sink = 0;
    d->sink_handler.unload();
    setState(Closed);
}

bool Player::load(const char* filename) {
    if (state() == Closed) return false;

    if (state() == Paused || state() == Playing)
        stop();

    if (state() == Loaded)
        unload();

    assert(state() == Open);

    d->src = new MMapFile(filename);
    // Test if the file _can_ be mmaped
    if (!d->src->openRO()) {
        delete d->src;
        d->src = new LocalFile(filename);
        if (!d->src->openRO()) {
            AKODE_DEBUG("Could not open " << filename);
            delete d->src;
            d->src = 0;
            return false;
        }
    }
    // Some of the later code expects it to be closed
    d->src->close();
    d->my_file = true;

    return load();
}

bool Player::load(File *file) {
    if (state() == Closed) return false;

    if (state() == Paused || state() == Playing)
        stop();

    if (state() == Loaded)
        unload();

    assert(state() == Open);

    if (!file->openRO())
        return false;
    file->close();

    d->src = file;
    d->my_file = false;

    return load();
}

bool Player::load() {
    if (d->decoder_plugin) {
        if (!d->decoder_handler.load(d->decoder_plugin))
            AKODE_DEBUG("Could not load " << d->decoder_plugin << "-decoder");
    }

    if (!d->decoder_handler.isLoaded()) {
        string format = Magic::detectFile(d->src);
        if (!format.empty())
            AKODE_DEBUG("Guessed format: " << format)
        else {
            AKODE_DEBUG("Cannot detect mimetype");
            delete d->src;
            d->src = 0;
            return false;
        }
        if (!d->decoder_handler.load(format))
            AKODE_DEBUG("Could not load " << format << "-decoder");
    }

    if (!d->decoder_handler.isLoaded()) {
        delete d->src;
        d->src = 0;
        return false;
    }

    d->frame_decoder = d->decoder_handler.openDecoder(d->src);
    if (!d->frame_decoder) {
        AKODE_DEBUG("Failed to open Decoder");
        d->decoder_handler.unload();
        delete d->src;
        d->src = 0;
        return false;
    }

    AudioFrame first_frame;

    if (!d->frame_decoder->readFrame(&first_frame)) {
        AKODE_DEBUG("Failed to decode first frame");
        delete d->frame_decoder;
        d->frame_decoder = 0;
        d->decoder_handler.unload();
        delete d->src;
        d->src = 0;
        return false;
    }

    if (!loadResampler()) {
        AKODE_DEBUG("The resampler failed to load");
        return false;
    }

    int state = d->sink->setAudioConfiguration(&first_frame);
    if (state < 0) {
        AKODE_DEBUG("The sink could not be configured for this format");
        delete d->frame_decoder;
        d->frame_decoder = 0;
        d->decoder_handler.unload();
        delete d->src;
        d->src = 0;
        return false;
    }
    else
    if (state > 0) {
        // Configuration not 100% accurate
        d->sample_rate = d->sink->audioConfiguration()->sample_rate;
        if (d->sample_rate != first_frame.sample_rate) {
            AKODE_DEBUG("Resampling to " << d->sample_rate);
            d->resampler->setSampleRate(d->sample_rate);
        }
        int out_channels = d->sink->audioConfiguration()->channels;
        int in_channels = first_frame.channels;
        if (in_channels != out_channels) {
            // ### We don't do mixing yet
            AKODE_DEBUG("Sample has wrong number of channels");
            delete d->frame_decoder;
            d->frame_decoder = 0;
            d->decoder_handler.unload();
            delete d->src;
            d->src = 0;
            return false;
        }
        int out_width = d->sink->audioConfiguration()->sample_width;
        int in_width = first_frame.sample_width;
        if (in_width != out_width) {
            AKODE_DEBUG("Converting to " << out_width << "bits");
            if (!d->converter)
                d->converter = new Converter(out_width);
            else
                d->converter->setSampleWidth(out_width);
        }
    }
    else
    {
        delete d->resampler;
        delete d->converter;
        d->resampler = 0;
        d->converter = 0;
    }

    // connect the streams to play
    d->buffered_decoder.setBlockingRead(true);
    d->buffered_decoder.openDecoder(d->frame_decoder);
    d->buffered_decoder.buffer()->put(&first_frame);

    setState(Loaded);

    return true;
}

bool Player::loadResampler() {
    if (!d->resampler) {
        d->resampler_handler.load(d->resampler_plugin);
        d->resampler = d->resampler_handler.openResampler();
    }
    return d->resampler != 0;
}

void Player::unload() {
    if (state() == Closed || state() == Open) return;
    if (state() == Paused || state() == Playing)
        stop();

    assert(state() == Loaded);

    d->buffered_decoder.closeDecoder();

    delete d->frame_decoder;
    if (d->my_file)
        delete d->src;

    d->frame_decoder = 0;
    d->src = 0;
    d->decoder_handler.unload();

    delete d->resampler;
    delete d->converter;
    d->resampler = 0;
    d->converter = 0;

    setState(Open);
}

void Player::play() {
    if (state() == Closed || state() == Open) return;
    if (state() == Playing) return;

    if (state() == Paused) {
        return resume();
    }

    assert(state() == Loaded);

    d->frame_decoder->seek(0);

    // Start buffering
    d->buffered_decoder.start();

    d->halt = d->pause = false;

    if (pthread_create(&d->player_thread, 0, run_player, d) == 0) {
        d->running = true;
        setState(Playing);
    } else {
        d->running = false;
        setState(Loaded);
    }
}

void Player::detach() {
    if (state() == Closed || state() == Open) return;
    if (state() == Loaded) return;

    if (state() == Paused) resume();

    assert(state() == Playing);

    if (d->running) {
        pthread_detach(d->player_thread);
        d->running = false;
    }

    private_data * d_new  = new private_data;
    d_new->sink = d->sink;
    d_new->volume_filter = d->volume_filter;
    d_new->sample_rate = d->sample_rate;
    d_new->state = d->state;

    d->detached = true;
    d = d_new;

    setState(Open);
}


void Player::stop() {
    if (state() == Closed || state() == Open) return;
    if (state() == Loaded) return;

    // Needs to set halt first to avoid the paused thread playing a soundbite
    d->halt = true;
    if (state() == Paused) resume();

    assert(state() == Playing);

    d->buffered_decoder.stop();

    if (d->running) {
        pthread_join(d->player_thread, 0);
        d->running = false;
    }

    setState(Loaded);
}

// Much like stop except we don't send a halt signal
void Player::wait() {
    if (state() == Closed || state() == Open) return;
    if (state() == Loaded) return;

    if (state() == Paused) resume();

    assert(state() == Playing);

    if (d->running) {
        pthread_join(d->player_thread, 0);
        d->running = false;
    }

    setState(Loaded);
}

void Player::pause() {
    if (state() == Closed || state() == Open || state() == Loaded) return;
    if (state() == Paused) return;

    assert(state() == Playing);

    //d->buffer->pause();
    d->pause = true;
    setState(Paused);
}

void Player::resume() {
    if (state() != Paused) return;

    d->pause = false;
    sem_post(&d->pause_sem);

    setState(Playing);
}


void Player::setVolume(float f) {
    if (f < 0.0 || f > 1.0) return;

    if (f != 1.0 && !d->volume_filter) {
        VolumeFilter *vf = new VolumeFilter();
        vf->setVolume(f);
        d->volume_filter = vf;
    }
    else if (f != 1.0) {
        d->volume_filter->setVolume(f);
    }
    else if (d->volume_filter) {
        VolumeFilter *f = d->volume_filter;
        d->volume_filter = 0;
        delete f;
    }
}

float Player::volume() const {
    if (!d->volume_filter) return 1.0;
    else
        return d->volume_filter->volume();
}

File* Player::file() const {
    return d->src;
}

Sink* Player::sink() const {
    return d->sink;
}

Decoder* Player::decoder() const {
    return &d->buffered_decoder;
}

Resampler* Player::resampler() const {
    return d->resampler;
}

Player::State Player::state() const {
    return d->state;
}

void Player::setDecoderPlugin(const char* plugin) {
    if (plugin && strncmp(plugin, "auto", 4) == 0) plugin = 0;
    d->decoder_plugin = plugin;
}

void Player::setResamplerPlugin(const char* plugin) {
    d->resampler_plugin = plugin;
}

void Player::setManager(Manager *manager) {
    d->manager = manager;
}

void Player::setMonitor(Monitor *monitor) {
    d->monitor = monitor;
}

void Player::setState(Player::State state) {
    d->setState(state);
}

} // namespace
