/*
 * dispatcher.c
 */

#include "dispatcher.h"
#include "setup.h"
#include "audiorecorder.h"
#include "postproc.h"
#include "a-tools.h"

#include <vdr/device.h>
#include <vdr/channels.h>

#include <unistd.h>
#include <sys/statfs.h>


#define COUNTER 30

static const char *rds_channels[] = {
        "S19.2E-1-1093-28406", /* DAS MODUL */
        "S19.2E-1-1093-28475", /* Eins Live */
        "S19.2E-1-1093-28476", /* WDR 2 */
        "S19.2E-1-1093-28423", /* YOU FM */
        "S19.2E-1-1093-28450", /* Bremen Vier */
        "S19.2E-1-1093-28402", /* Bayern 3 */
        "S19.2E-1-1093-28437", /* NDR 2 */
        "S19.2E-1-1093-28440", /* N-JOY */
        "S19.2E-1-1093-28420", /* hr2 */
        "S19.2E-1-1093-28421", /* hr3 */
        "S19.2E-1-1093-28419", /* hr1 */
        "S19.2E-1-1093-28468", /* SWR 3 */
        /* "S19.2E-1-1093-28471", DASDING */
};

static const int num_rds_channels = 12;

/* --- cDispatcher----------------------------------------------------------- */

cDispatcher::cDispatcher(void)
:cThread(), cStatus()
{
        tChannelID channel_id;
        
        active = false;
        audioreceivers = NULL;
        
        /* initialize one receiver for each channel */
        audioreceivers = new cAudioReceiver *[num_rds_channels];
        for (int c = 0; c < num_rds_channels; ++c) {
                channel_id = tChannelID::FromString(rds_channels[c]);
                
                if (! channel_id.Valid()) {
                        audioreceivers[c] = NULL;
                        dsyslog("[audiorecorder]: channel %s is not valid (%s, "
                                "%s())", rds_channels[c], __FILE__, __func__);
                        continue;
                }
                
                cChannel *channel = Channels.GetByChannelID(channel_id);
                if (channel)
                        audioreceivers[c] = new cAudioReceiver(channel);
                else
                        audioreceivers[c] = NULL;
        }
       
	if (SetupValues.start_type == 1) 
        	Activate(true);
}


cDispatcher::~cDispatcher()
{
	Activate(false);
        
        if (audioreceivers) {
                for (int c = 0; c < num_rds_channels; ++c)
                        delete audioreceivers[c];
        }
        
        delete[] audioreceivers;
}


void cDispatcher::Activate(bool on)
{
        if (on) {
                if (! active) {
                        active = true;
                        Start();
                }
        }
        else if (active) {
                active = false;
                Cancel(3);
        }
}


void cDispatcher::Action(void)
{
        bool interrupted = false;
        counter = COUNTER;
        
        dsyslog("[audiorecorder]: dispatcher thread started (%s, %s())",
                __FILE__, __func__);
        
        while (active) {
                sleep(1);
                
                int num_queued = cPostproc::get_num_queued();
                
                if (interrupted && num_queued > (SetupValues.max_postproc / 4))
                        continue;
                
                interrupted = false;
                
                if (num_queued >= SetupValues.max_postproc) {
                        dsyslog("[audiorecorder]: max. postprocessings in queue"
                                " achieved (%d), detaching all receivers (%s, "
                                "%s())", num_queued, __FILE__, __func__);
                        
                        interrupted = true;
                        detach_receivers(-1, 0);
                        
                        continue;
                }
                
                if (! check_free_disc_space()) {
                        dsyslog("[audiorecorder]: min. free disc space on %s "
                                "achived (%d mb), recording will be stopped "
                                "(%s, %s())",
                                cPluginAudiorecorder::get_recdir().c_str(),
                                SetupValues.min_free_space, __FILE__,
                                __func__);
                        
                        active = false;
                        continue;
                }
                
                int receivers = get_attached_receivers(-1);
                if (SetupValues.max_receivers > receivers) {
                        if (counter > 0)
                                counter--;
                        else
                                attach_receivers();
                }
                else if (SetupValues.max_receivers < receivers)
                        detach_receivers(-1, SetupValues.max_receivers);
        }
        
        detach_receivers(-1, 0);
        
        dsyslog("[audiorecorder]: dispatcher thread stopped (%s, %s())",
                __FILE__, __func__);
}


void cDispatcher::ChannelSwitch(const cDevice *device, int channel_number)
{
	/*
	 * workaround to detach active audioreceivers if the attached device
	 * is switched to another transponder. this should be done inside of
	 * vdr, but there are situations which lead to emergency exits of vdr
	 * without this peace of code (for example if a recording starts).
         *
	 */
         
        int device_number;
        
        if (channel_number == 0) {
                counter = COUNTER;
                return;
        }
        
        device_number = device->DeviceNumber();
        
        if (get_attached_receivers(device_number) == 0)
                return;
        
	if (Channels.GetByNumber(channel_number)->Transponder() ==
	    get_transponder_of_first_receiver(device_number))
		return;
        
        detach_receivers(device_number, 0);
}


int cDispatcher::get_attached_receivers(int device_number)
{
        int c, count = 0;
        
        if (! audioreceivers)
                return 0;
        
        for (c = 0; c < num_rds_channels; ++c) {
                if (! audioreceivers[c])
                        continue;
                
                if (audioreceivers[c]->is_attached(device_number))
			++count;
        }
       	
        return count;
}


int cDispatcher::get_recording_receivers(void)
{
        int c, count = 0;
        
        if (! audioreceivers)
                return 0;
        
        for (c = 0; c < num_rds_channels; ++c) {
                if (audioreceivers[c] && audioreceivers[c]->is_recording())
                        ++count;
        }
        
        return count;
}


int cDispatcher::get_recording_status(const cChannel *channel)
{
        int c;
        
        if (! audioreceivers)
                return 0;
        
        for (c = 0; c < num_rds_channels; ++c) {
                if (audioreceivers[c] &&
                    audioreceivers[c]->get_channel() == channel) {
                        if (audioreceivers[c]->is_recording())
                                return 3;
                        else if (audioreceivers[c]->is_attached(-1))
                                return 2;
                        else
                                return 1;
                    }
        }
        
        return 0;
}


void cDispatcher::detach_receivers(int detach_device_number,
        int devices_remaining)
{
        int c;
        int device_number;
        cDevice *device = NULL;
        
        if (! audioreceivers)
                return;
        
        Lock();
        
        for (c = num_rds_channels - 1; c > -1; c--) {
                if (get_attached_receivers(-1) <= devices_remaining)
                        break;
                
                if (! audioreceivers[c])
                        continue;
                
                device_number = audioreceivers[c]->get_device_number();
                
                if (device_number < 0)
                        continue;
                
                if (detach_device_number != device_number &&
                    detach_device_number > 0)
                        continue;
                
                device = cDevice::GetDevice(device_number);
                
                if (! device)
                        continue;
                
                device->Detach(audioreceivers[c]);
        }
        
        Unlock();
}


void cDispatcher::attach_receivers(void)
{
        int c;
        cAudioReceiver *receiver;
        cDevice *device;
        bool needs_detach;
        
        Lock();
        
        for (c = 0; c < num_rds_channels; ++c) {
                if (get_attached_receivers(-1) >= SetupValues.max_receivers)
                        break;
                
                receiver = audioreceivers[c];
                
                if (! receiver || receiver->is_attached(-1))
                        continue;
                
                device = cDevice::GetDevice(receiver->get_channel(), 0,
                        &needs_detach);
                
                if (! device || (device && needs_detach))
                        continue;
                
#ifndef AUDIORCORDER_DEVEL
		if (device == cDevice::ActualDevice() &&
                    ! device->IsTunedToTransponder(receiver->get_channel()))
                        continue;
#endif /* AUDIORECORDER_DEVEL */
                
                if (! device->IsTunedToTransponder(receiver->get_channel()) &&
                    ! device->SwitchChannel(receiver->get_channel(), false))
                        continue;
                
                receiver->set_device_number(device->DeviceNumber());
                device->AttachReceiver(receiver);
        }
        
        Unlock();
}


int cDispatcher::get_transponder_of_first_receiver(int device_number)
{
        int c, transponder = 0;
        
        if (! audioreceivers)
                return 0;
        
        for (c = 0; c < num_rds_channels; ++c) {
                if (! audioreceivers[c])
                        continue;
                
                if (audioreceivers[c]->is_attached(device_number)) {
                        transponder =
                                audioreceivers[c]->get_channel()->Transponder();
                        break;
                }
        }
        
        return transponder;
}


bool cDispatcher::check_free_disc_space(void)
{
        struct statfs stat_fs;
        
        if (statfs(cPluginAudiorecorder::get_recdir().c_str(), &stat_fs) != 0)
                return false;
        
        if ((uint64_t)stat_fs.f_bavail * (uint64_t)stat_fs.f_bsize <
            (uint64_t)SetupValues.min_free_space * (uint64_t)1024 *
            (uint64_t)1024)
                return false;
        
        return true;
}
