/*

    Copyright (C) 2000-2001 Stefan Westerfeld
                            stefan@space.twc.de
                       2003 Arnold Krille <arnold@arnoldarts.de

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

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

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

    Permission is also granted to link this program with the Qt
    library, treating Qt like a library that normally accompanies the
    operating system kernel, whether or not that is in fact the case.

    */

#include "mcoputils.h"
#include <signal.h>
#include <math.h>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <stdio.h>
#include "soundserver.h"
#include "audiosubsys.h"
#include "audioio.h"
#include "tcpserver.h"
#include "cpuusage.h"
#include "debug.h"
#include "artsversion.h"
#include "crashhandler.h"

using namespace std;
using namespace Arts;

extern "C" void stopServer(int)
{
	Dispatcher::the()->terminate();
}

static void initSignals()
{
    signal(SIGHUP ,stopServer);
    signal(SIGINT ,stopServer);
    signal(SIGTERM,stopServer);
}

static void exitUsage(const char *progname)
{
	fprintf(stderr,"usage: %s [ options ]\n",progname);
	fprintf(stderr,"\n");
	fprintf(stderr,"server/network options:\n");
	fprintf(stderr,"-n                  enable network transparency\n");
	fprintf(stderr,"-p <port>           set TCP port to use (implies -n)\n");
	fprintf(stderr,"-u                  public, no authentication (dangerous!)\n");
	fprintf(stderr,"-N                  use larger network buffers\n");
	fprintf(stderr,"-w <n>              increase network buffers by factor of <n>\n");
	fprintf(stderr,"\n");
	fprintf(stderr,"audio options:\n");
	fprintf(stderr,"-a <audioiomethod>  select audio i/o method (oss, alsa, ...)\n");
	fprintf(stderr,"-r <samplingrate>   set samplingrate to use\n");
	fprintf(stderr,"-b <bits>           set number of bits (8 or 16)\n");
	fprintf(stderr,"-d                  enable full duplex operation\n");
	fprintf(stderr,"-V <volume>[dB]     set output volume\n");
	fprintf(stderr,"-D <devicename>     audio device (usually /dev/dsp)\n");
	fprintf(stderr,"-F <fragments>      number of fragments\n");
	fprintf(stderr,"-S <size>           fragment size in bytes\n");
	fprintf(stderr,"-s <seconds>        auto-suspend time in seconds\n");
	fprintf(stderr,"-f                  force starting artsd (if no soundcard is there, uses the null output device)\n");
	fprintf(stderr,"\n");
	fprintf(stderr,"misc options:\n");
	fprintf(stderr,"-h                  display this help and exit\n");
	fprintf(stderr,"-A                  list possible audio i/o methods (for -a)\n");
	fprintf(stderr,"-v                  show version\n");
	fprintf(stderr,"-l <level>          information level\n");
	fprintf(stderr,"  3: quiet, 2: warnings, 1: info, 0: debug\n");
	fprintf(stderr,"-m <appName>        application to display messages\n");
	fprintf(stderr,"-c <appName>        application to display crash dialogs\n");
	exit(1);	
}

static void exitListAudioIO()
{
	fprintf(stderr,"possible choices for the audio i/o method:\n");
	fprintf(stderr,"\n");

	for(int i = 0; i < AudioIO::queryAudioIOCount(); i++)
	{
		fprintf(stderr, "  %-10s%s\n",
			AudioIO::queryAudioIOParamStr(i, AudioIO::name),
			AudioIO::queryAudioIOParamStr(i, AudioIO::fullName));
	}
	fprintf(stderr,"\n");
	exit(0);	
}

static Dispatcher::StartServer	cfgServers		= Dispatcher::startUnixServer;
static int  					cfgSamplingRate	= 0;
static int  					cfgBits			= 0;
static int  					cfgFragmentCount= 0;
static int  					cfgFragmentSize	= 0;
static int  					cfgPort			= 0;
static int  					cfgDebugLevel	= 2;
static const char			   *cfgDebugApp		= 0;
static const char			   *cfgCrashApp		= 0;
static bool  					cfgFullDuplex	= 0;
static bool  					cfgForceStart	= 0;
static const char			   *cfgDeviceName   = 0;
#if defined(__osf__)
// osf/1 does not have sound devices so avoid the default auto-detect
static const char              *cfgAudioIO      = "null";
#else
static const char              *cfgAudioIO      = 0;
#endif
static int                      cfgAutoSuspend  = 0;
static int                      cfgBuffers      = 0;
static float					cfgVolume		= 0.0;

static bool						cmdListAudioIO  = false;

static void handleArgs(int argc, char **argv)
{
	int optch;
	while((optch = getopt(argc,argv,"r:p:nuF:S:hD:dl:a:Ab:s:m:vNw:fV:c:")) > 0)
	{
		switch(optch)
		{
			case 'p': cfgPort = atoi(optarg); // setting a port => network transparency
			case 'n': cfgServers = static_cast<Dispatcher::StartServer>( cfgServers | Dispatcher::startTCPServer);
				break;
			case 'a': cfgAudioIO = optarg;
				break;
			case 'r': cfgSamplingRate = atoi(optarg);
				break;
			case 'b': cfgBits = atoi(optarg);
				break;
			case 's': cfgAutoSuspend = atoi(optarg);
				break;
			case 'F': cfgFragmentCount = atoi(optarg);
				break;
			case 'S': cfgFragmentSize = atoi(optarg);
				break;
			case 'D': cfgDeviceName = optarg;
				break;
			case 'd': cfgFullDuplex = true;
				break;
			case 'l': cfgDebugLevel = atoi(optarg);
				break;
			case 'm': cfgDebugApp = optarg;
				break;
			case 'c': cfgCrashApp = optarg;
				break;
			case 'u': cfgServers = static_cast<Dispatcher::StartServer>( cfgServers | Dispatcher::noAuthentication);
				break;
			case 'A': cmdListAudioIO = true;
				break;
			case 'v': printf("artsd %s\n",ARTS_VERSION);
					  exit(0);
				break;
			case 'N': cfgBuffers = 5;
				break;
			case 'w': cfgBuffers = atoi(optarg);
				break;
			case 'f': cfgForceStart = true;
				break;
			case 'V':
				if ( strstr( optarg,"dB" )&& strlen( strstr( optarg,"dB" ) )==2 ) {
					char* val = ( char* )calloc( strlen( optarg )-1, sizeof( char ) );
					strncpy( val, optarg, strlen( optarg )-2 );
					cfgVolume = pow( 10, atof( val )/20.0 );
					free( val );
				} else cfgVolume = atof( optarg );
				break;
			case 'h':
			default:
					exitUsage(argc?argv[0]:"artsd");
				break;
		}
	}
}

static bool publishReferences(SoundServerV2 server,
							  AudioManager audioManager,
							  bool silent)
{
	ObjectManager *om = ObjectManager::the();
	bool result;

	result=om->addGlobalReference(server,"Arts_SoundServerV2")
	    && om->addGlobalReference(server,"Arts_SoundServer")
	    && om->addGlobalReference(server,"Arts_SimpleSoundServer")
        && om->addGlobalReference(server,"Arts_PlayObjectFactory")
        && om->addGlobalReference(audioManager,"Arts_AudioManager");
	
	if(!result && !silent)
	{
		cerr <<
"[artsd] Error: Can't add object reference (probably artsd is already running)."
              << endl <<
"       If you are sure it is not already running, remove the relevant files:"
              << endl << endl <<
"       "<< MCOPUtils::createFilePath("Arts_SoundServerV2") << endl <<
"       "<< MCOPUtils::createFilePath("Arts_SoundServer") << endl <<
"       "<< MCOPUtils::createFilePath("Arts_SimpleSoundServer") << endl <<
"       "<< MCOPUtils::createFilePath("Arts_PlayObjectFactory") << endl <<
"       "<< MCOPUtils::createFilePath("Arts_AudioManager") << endl << endl;
	}
	return result;
}

static int cleanReference(const string& reference)
{
	Object test;
	test = Reference("global:"+reference);
	if(test.isNull())
	{
		Dispatcher::the()->globalComm().erase(reference);
		return 1;
	}
	else
		return 0;
}

static void cleanUnusedReferences()
{
	int i = 0;

	cerr << "[artsd] There are already artsd objects registered, "
			"looking if they are active..." << endl;

	sleep(1); // maybe an artsd process has just started (give it some time)

	i += cleanReference("Arts_SoundServerV2");
	i += cleanReference("Arts_SoundServer");
	i += cleanReference("Arts_SimpleSoundServer");
	i += cleanReference("Arts_PlayObjectFactory");
	i += cleanReference("Arts_AudioManager");

	if(i)
		cerr << "[artsd] ... cleaned " <<i<< " unused mcop global references." << endl;
	cerr << endl;
}

int main(int argc, char **argv)
{
	handleArgs(argc, argv);

	Debug::init("[artsd]", static_cast<Debug::Level>(cfgDebugLevel));

	arts_debug("artsd version is %s",ARTS_VERSION);
	if (cfgDebugApp)
		Debug::messageApp(cfgDebugApp);
	if (cfgCrashApp)
	{
		CrashHandler::setCrashHandler(CrashHandler::defaultCrashHandler);
		CrashHandler::setCrashApp(cfgCrashApp);
		CrashHandler::setApplicationName("artsd");
		CrashHandler::setApplicationPath(argc ? argv[0] : "artsd");
  		CrashHandler::setApplicationVersion (ARTS_VERSION);
		CrashHandler::setProgramName("Soundserver");
  		CrashHandler::setBugAddress("submit@bugs.kde.org");
	}

	if(cfgPort)			 TCPServer::setPort(cfgPort);

	CPUUsage	cpuUsage;
	Dispatcher	dispatcher(0,cfgServers);
	string      warnNullDevice;

	initSignals();

	/* execute commands, if any */
	if(cmdListAudioIO)	 exitListAudioIO();

	/* apply configuration */
	if(cfgAudioIO)       AudioSubSystem::the()->audioIO(cfgAudioIO);
	if(cfgSamplingRate)  AudioSubSystem::the()->samplingRate(cfgSamplingRate);
	if(cfgFragmentCount) AudioSubSystem::the()->fragmentCount(cfgFragmentCount);
	if(cfgFragmentSize)  AudioSubSystem::the()->fragmentSize(cfgFragmentSize);
	if(cfgFullDuplex)	 AudioSubSystem::the()->fullDuplex(cfgFullDuplex);
	if(cfgDeviceName)	 AudioSubSystem::the()->deviceName(cfgDeviceName);
	if(cfgBits)			 AudioSubSystem::the()->format(cfgBits);

	SoundServerStartup startup;
	startup.lock();

	if(cfgForceStart && !AudioSubSystem::the()->check())
	{
		//Don't show an error (this looks bad and may confuse users without sound cards), kmix makes it obvious if sound isn't working
		//warnNullDevice  = "Error while initializing the sound driver:\n";
		//warnNullDevice += AudioSubSystem::the()->error();
		//warnNullDevice += "\n\nThe sound server will continue, using the null output device.";
		
		AudioSubSystem::the()->audioIO("null");
	}

	if(!AudioSubSystem::the()->check())
	{
		string msg = "Error while initializing the sound driver:\n";
		msg += AudioSubSystem::the()->error();
		arts_fatal("%s", msg.c_str());
		exit(1);
	}

	/* start sound server implementation */
	SoundServerV2 server;
	AudioManager audioManager;

	if (fabs(cfgVolume) > 1e-10)
		server.outVolume().scaleFactor(cfgVolume);

	if (cfgAutoSuspend)
		server.autoSuspendSeconds(cfgAutoSuspend);

	if (cfgBuffers)
		server.bufferSizeMultiplier(cfgBuffers);

	/* make global MCOP references available */
	if(!publishReferences(server,audioManager,true))
	{
		cleanUnusedReferences();
		if(!publishReferences(server,audioManager,false)) return 1;
	}

	startup.unlock();

	/* warn if we are using the null device */
	if (!warnNullDevice.empty())
	{
		/* hack to get this message to the user in any case */
		Debug::init("[artsd]", static_cast<Debug::Level>(1));
		arts_info("%s", warnNullDevice.c_str());
		Debug::init("[artsd]", static_cast<Debug::Level>(cfgDebugLevel));
	}

	/* warn if there was a problem with artswrapper */
	char *wrapper = getenv("STARTED_THROUGH_ARTSWRAPPER");
	if (wrapper && !strcmp(wrapper, "2"))
		arts_warning(
			"[artsd] Can't set real-time scheduling priority.\n"
			"You need to run artswrapper as root or\n"
			"setuid root. This means that you will\n"
			"likely not be able to produce acceptable\n"
			"sound (i.e. without clicks and breaks).");

	if (wrapper && !strcmp(wrapper, "3"))
		arts_warning(
			"[artsd] This system has no support for real-time\n"
			"scheduling priority. This means that you\n"
			"will likely not be able to produce acceptable\n"
			"sound (i.e. without clicks and breaks).");

	dispatcher.run();
	return 0;
}

#ifdef __SUNPRO_CC
/* See bottom of simplesoundserver_impl.cpp for the reason this is here.  */
#include "simplesoundserver_impl.h"
REGISTER_IMPLEMENTATION(SimpleSoundServer_impl);
#include "soundserver_impl.h"
REGISTER_IMPLEMENTATION(SoundServer_impl);
#include "soundserverv2_impl.h"
REGISTER_IMPLEMENTATION(SoundServerV2_impl);
#include "soundserverstartup_impl.h"
REGISTER_IMPLEMENTATION(SoundServerStartup_impl);
#endif