#define DBUS_API_SUBJECT_TO_CHANGE 1

#include <dbus/dbus.h>
#include <dbus/dbus-glib-lowlevel.h>
#include <hal/libhal.h>
#include <hal/libhal-storage.h>
#include <glib.h>
#include <stdlib.h>
#include <vdr/remote.h>
#include <vdr/recording.h>

#include "hal.h"

using namespace std;

cMutex hal::_halMutex;
deviceSources* hal::_pDevices = NULL;
device *hal::_pCurDevice = NULL;
GMainLoop *hal::loop = NULL;
LibHalContext* hal::_halContext = NULL;
DBusConnection *hal::_dbusConnection = NULL;
DBusError hal::_dbusError;
bool hal::triggered = false;
char* hal::_mountScript = NULL;
extern cRecordings Recordings;

hal::hal() : cThread()
{
}

hal::~hal() 
{
}

void hal::Stop()
{
 	if( _halContext ) {
 	   	libhal_ctx_shutdown( _halContext, 0 );
 	   	libhal_ctx_free( _halContext );
	}
	if(loop) {
		while(g_main_loop_is_running(loop))
			g_main_loop_quit( loop );
		g_main_context_wakeup(g_main_loop_get_context(loop));
		g_main_loop_unref( loop );
	}
	Cancel(5);
}

device* hal::getDevice(){
	return  _pCurDevice;
}

void hal::setDevices(deviceSources *pDevices)
{
	_pDevices = pDevices;
}

void hal::Action()
{
	DBusConnection *_dbusConnection;
  dbus_error_init(&_dbusError);
  _halContext = libhal_ctx_new();
  if( !_halContext ) {
	  fprintf(stderr, "mediad: not halContext\n");
    return ;
  }

  _dbusConnection = dbus_bus_get( DBUS_BUS_SYSTEM, &_dbusError );
  if( dbus_error_is_set(&_dbusError) ) {
	  fprintf(stderr, "mediad: no dbus connection\n");
    return ;
  }

	dbus_connection_setup_with_g_main(_dbusConnection, NULL);

	if(!libhal_ctx_set_dbus_connection(_halContext, _dbusConnection))
  {
     fprintf(stderr, "mediad: Couldn't connect to HAL!\n");
     exit(1);
  }


  libhal_ctx_set_dbus_connection( _halContext, _dbusConnection );
  
  libhal_ctx_set_device_added( _halContext, this->halDeviceAdded );
  libhal_ctx_set_device_removed( _halContext, this->halDeviceRemoved );
  libhal_ctx_set_device_new_capability( _halContext, 0 );
  libhal_ctx_set_device_lost_capability( _halContext, 0 );
  libhal_ctx_set_device_property_modified( _halContext, 0 );
  libhal_ctx_set_device_condition( _halContext, 0 );
  
  if( !libhal_ctx_init( _halContext, &_dbusError ) ) {
	  fprintf(stderr, "mediad: Could not init hal\n");
    return;
  }

	if (!libhal_device_property_watch_all(_halContext, &_dbusError)) {
    fprintf(stderr, "mediad: Failed to watch all HAL properties!\n"
     "Error: %s\n", _dbusError.message);
     exit(1);
  }
	initAllKnownDevices();
  fprintf(stderr,"Going into mainloop\n");
  loop = g_main_loop_new(NULL, FALSE);
  if ( !loop ) {
     fprintf(stderr, "mediad: Error creating main loop!\n") ;
     return;
   }
	// TODO: how to end loop
  g_main_loop_run(loop);
     fprintf(stderr, "mediad: Main loop exited!\n") ;
}

void hal::halDeviceAdded( LibHalContext* ctx, const char* udi )
{
	_halMutex.Lock();
  char *device = libhal_device_get_property_string( ctx, udi, "block.device", 0 );
  fprintf(stderr,"Device added %s\n", device);
	if( (_pCurDevice = _pDevices->FindSource(device)) != NULL)
	{
  fprintf(stderr,"matched %s\n", device);
		configureDevice(_pCurDevice, udi);
		_pCurDevice->setVolume(true);
		triggered = true;
		cRemote::CallPlugin("mediad");
	}
	free(device);
	_halMutex.Unlock();
}

void hal::halDeviceRemoved( LibHalContext* ctx, const char* udi )
{
	_halMutex.Lock();
	if( (_pCurDevice = _pDevices->FindSource(udi)) != NULL)
	{
		// only reload recordings in case it has a redirected video directory attached
		if(_pCurDevice->hasRedirectedVideoDir())
			Recordings.Load();
		fprintf(stderr, "mediad: Removed device %s\n", _pCurDevice->getDeviceName());
		_pCurDevice->setVolume(false);
		char* umount;
		asprintf(&umount, "%s umount /dev/%s", _mountScript, _pCurDevice->getDeviceName());
		system(umount);
	}
	_halMutex.Unlock();
}

void hal::initAllKnownDevices()
{
	_halMutex.Lock();
	int num_devices;
	char **udis =
	    (char **) libhal_get_all_devices(_halContext, &num_devices,
	                                   &_dbusError);
	if ( !udis ) {
		fprintf(stderr, "mediad: Could not find any devices!\n");
		return;
	}
	int i = 0;
	fprintf(stderr, "mediad: Checking states for all monitored devices\n");
	while ( udis && ( i < num_devices ) )
	{
		char *device = NULL;
		dbus_error_init (&_dbusError);
		if (libhal_device_get_property_bool
		  (_halContext, udis[i], "block.is_volume", &_dbusError)) {
		  device =
		    (char *)
		    libhal_device_get_property_string(_halContext,
		    udis[i],
			  "block.device", &_dbusError);
			fprintf(stderr, ": device %s\n", device);
			if( (_pCurDevice = _pDevices->FindSource(device)) != NULL)
			{
			fprintf(stderr, ": matched \n");
				configureDevice(_pCurDevice, udis[i]);				
				_pCurDevice->setVolume(true);
			}
		}
		libhal_free_string(device);
		i++;
	}
	if ( udis )
	  libhal_free_string_array(udis);
	_halMutex.Unlock();
}

void hal::configureDevice(device *pDevice, const char* udi) {
	// only interested in the volume
	if( libhal_device_get_property_bool( _halContext, udi, "block.is_volume", 0) ) {
		pDevice->setUDI(udi);
		fprintf(stderr, "mediad: UDI %s %s\n", udi, pDevice->getDeviceName());
		if(libhal_device_get_property_bool(_halContext, udi, "volume.is_disc", 0)) {
			fprintf(stderr, "is disc\n");
			if(libhal_device_get_property_bool(_halContext, udi, "volume.disc.is_blank", 0)) {
				pDevice->setCurMediaType(blank);
				fprintf(stderr, "blank\n");
			} else if(libhal_device_get_property_bool(_halContext, udi, "volume.disc.has_audio", 0)) {
				pDevice->setCurMediaType(audio);
				// read cddb data if available
				fprintf(stderr, "audio\n");
			} else if(libhal_device_get_property_bool(_halContext, udi, "volume.disc.has_data", 0)) {
				pDevice->setCurMediaType(data);
				// now get the label of the dvd
				LibHalVolume *volume = libhal_volume_from_udi(_halContext, udi);
				pDevice->setInformation(libhal_volume_get_label(volume));
				libhal_volume_free(volume);
				fprintf(stderr, "has data\n");
				if(libhal_device_get_property_bool(_halContext, udi, "volume.disc.is_vcd", 0) || libhal_device_get_property_bool(_halContext, udi, "volume.disc.is_svcd", 0)) {
					pDevice->setCurMediaType(svcd);
					fprintf(stderr, "vcd/svcd\n");
				}
				if(libhal_device_get_property_bool(_halContext, udi, "volume.disc.is_videodvd", 0)) {
					pDevice->setCurMediaType(videodvd);
					fprintf(stderr, "video dvd\n");
				}
				fprintf(stderr, "bulk data\n");
			} else {
				fprintf(stderr, "completely unknown\n");
			}
		} else {
			fprintf(stderr, "is data device\n");
			pDevice->setCurMediaType(data);
		}
	}
}

void hal::setMountScript(const char *mountScript)
{
	if(_mountScript != NULL)
		free(_mountScript);
	_mountScript = strdup(mountScript);
}
