/*
    Copyright (C) 2000 Paul Davis 

    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., 675 Mass Ave, Cambridge, MA 02139, USA.

    $Id: globals.cc,v 1.59 2006/02/02 21:11:43 sampo Exp $
*/

#include <cstdio> // Needed so that libraptor (included in lrdf) won't complain
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <locale.h>

#ifdef VST_SUPPORT
#include <fst.h>
#endif

#include <lrdf.h>

#include <pbd/error.h>

#include <midi++/port.h>
#include <midi++/port_request.h>
#include <midi++/manager.h>
#include <midi++/mmc.h>

#include <ardour/ardour.h>
#include <ardour/audio_library.h>
#include <ardour/configuration.h>
#include <ardour/plugin_manager.h>
#include <ardour/source.h>
#include <ardour/utils.h>
#include <ardour/session.h>

#include <ardour/mix.h>

#if defined (__APPLE__)
	#include <Carbon/Carbon.h> // For Gestalt
#endif
	
#include "i18n.h"

ARDOUR::Configuration* ARDOUR::Config = 0;
ARDOUR::AudioLibrary* ARDOUR::Library = 0;

using namespace ARDOUR;
using namespace std;

MIDI::Port *default_mmc_port = 0;
MIDI::Port *default_mtc_port = 0;
MIDI::Port *default_midi_port = 0;

Change ARDOUR::StartChanged = ARDOUR::new_change ();
Change ARDOUR::LengthChanged = ARDOUR::new_change ();
Change ARDOUR::PositionChanged = ARDOUR::new_change ();
Change ARDOUR::NameChanged = ARDOUR::new_change ();
Change ARDOUR::BoundsChanged = Change (0); // see init(), below


static int 
setup_midi ()
{
	std::map<string,Configuration::MidiPortDescriptor*>::iterator i;
	int nports;

	if ((nports = Config->midi_ports.size()) == 0) {
		warning << _("no MIDI ports specified: no MMC or MTC control possible") << endmsg;
		return 0;
	}

	for (i = Config->midi_ports.begin(); i != Config->midi_ports.end(); ++i) {
		Configuration::MidiPortDescriptor* port_descriptor;

		port_descriptor = (*i).second;

		MIDI::PortRequest request (port_descriptor->device, 
					   port_descriptor->tag, 
					   port_descriptor->mode, 
					   port_descriptor->type);

		if (request.status != MIDI::PortRequest::OK) {
			error << compose(_("MIDI port specifications for \"%1\" are not understandable."), port_descriptor->tag) << endmsg;
			continue;
		}
		
		MIDI::Manager::instance()->add_port (request);
	}

	if (nports > 1) {

		/* More than one port, so try using specific names for each port */

		map<string,Configuration::MidiPortDescriptor *>::iterator i;

		if (Config->get_mmc_port_name() != N_("default")) {
			default_mmc_port =  MIDI::Manager::instance()->port (Config->get_mmc_port_name());
		} 

		if (Config->get_mtc_port_name() != N_("default")) {
			default_mtc_port =  MIDI::Manager::instance()->port (Config->get_mtc_port_name());
		} 

		if (Config->get_midi_port_name() != N_("default")) {
			default_midi_port =  MIDI::Manager::instance()->port (Config->get_midi_port_name());
		} 
		
		/* If that didn't work, just use the first listed port */

		if (default_mmc_port == 0) {
			default_mmc_port = MIDI::Manager::instance()->port (0);
		}

		if (default_mtc_port == 0) {
			default_mtc_port = MIDI::Manager::instance()->port (0);
		}

		if (default_midi_port == 0) {
			default_midi_port = MIDI::Manager::instance()->port (0);
		}
		
	} else {

		/* Only one port described, so use it for both MTC and MMC */

		default_mmc_port = MIDI::Manager::instance()->port (0);
		default_mtc_port = default_mmc_port;
		default_midi_port = default_mmc_port;
	}

	if (default_mmc_port == 0) {
		warning << compose (_("No MMC control (MIDI port \"%1\" not available)"), Config->get_mmc_port_name()) 
			<< endmsg;
		return 0;
	} 

	if (default_mtc_port == 0) {
		warning << compose (_("No MTC support (MIDI port \"%1\" not available)"), Config->get_mtc_port_name()) 
			<< endmsg;
	}

	if (default_midi_port == 0) {
		warning << compose (_("No MIDI parameter support (MIDI port \"%1\" not available)"), Config->get_midi_port_name()) 
			<< endmsg;
	}

	return 0;
}

int
ARDOUR::init (AudioEngine& engine, bool use_vst, bool try_optimization, void (*sighandler)(int,siginfo_t*,void*))
{
	bool generic_mix_functions = true;

	(void) bindtextdomain(PACKAGE, LOCALEDIR);

	Config = new Configuration;

	if (Config->load_state ()) {
		return -1;
	}

	Config->set_use_vst (use_vst);

	if (setup_midi ()) {
		return -1;
	}

#ifdef VST_SUPPORT
	if (Config->get_use_vst() && fst_init (sighandler)) {
		return -1;
	}
#endif

	if (try_optimization) {

#if defined (ARCH_X86) && defined (BUILD_SSE_OPTIMIZATIONS)
	
		unsigned int use_sse = 0;

#ifndef USE_X86_64_ASM
		asm volatile (
				 "mov $1, %%eax\n"
				 "pushl %%ebx\n"
				 "cpuid\n"
				 "popl %%ebx\n"
				 "andl $33554432, %%edx\n"
				 "movl %%edx, %0\n"
		 	     : "=m" (use_sse)
	   		     : 
 	    		 : "%eax", "%ecx", "%edx", "memory");
#else

		asm volatile (
				 "movq $1, %%rax\n"
				 "pushq %%rbx\n"
				 "cpuid\n"
				 "popq %%rbx\n"
				 "andq $33554432, %%rdx\n"
				 "movq %%rdx, %0\n"
		 	     : "=m" (use_sse)
	   		     : 
 	    		 : "%rax", "%rcx", "%rdx", "memory");

#endif /* USE_X86_64_ASM */
		
		if (use_sse) {
			cerr << "Enabling SSE optimized routines" << endl;
	
			// SSE SET
			Session::compute_peak 			= x86_sse_compute_peak;
			Session::apply_gain_to_buffer 	= x86_sse_apply_gain_to_buffer;
			Session::mix_buffers_with_gain 	= x86_sse_mix_buffers_with_gain;
			Session::mix_buffers_no_gain 	= x86_sse_mix_buffers_no_gain;

			generic_mix_functions = false;

		} 

#elif defined (__APPLE__) && defined (BUILD_VECLIB_OPTIMIZATIONS)
		long sysVersion = 0;

		if (noErr != Gestalt(gestaltSystemVersion, &sysVersion))
			sysVersion = 0;
		
		if (sysVersion >= 0x00001040) { // Tiger at least
			Session::compute_peak 		= veclib_compute_peak;
			Session::apply_gain_to_buffer 	= veclib_apply_gain_to_buffer;
			Session::mix_buffers_with_gain 	= veclib_mix_buffers_with_gain;
			Session::mix_buffers_no_gain 	= veclib_mix_buffers_no_gain;
			
			generic_mix_functions = false;

			info << "Apple VecLib H/W specific optimizations in use" << endmsg;
		} 
#endif
	} 

	if (generic_mix_functions) {

		Session::compute_peak 			= compute_peak;
		Session::apply_gain_to_buffer 	= apply_gain_to_buffer;
		Session::mix_buffers_with_gain 	= mix_buffers_with_gain;
		Session::mix_buffers_no_gain 	= mix_buffers_no_gain;
		
		info << "No H/W specific optimizations in use" << endmsg;
	}
	
	lrdf_init();
	Library = new AudioLibrary;

	/* singleton - first object is "it" */
	new PluginManager (engine);
	
	BoundsChanged = Change (StartChanged|PositionChanged|LengthChanged);

	return 0;
}

int
ARDOUR::cleanup ()
{
	delete Library;
	lrdf_cleanup ();
	return 0;
}

ARDOUR::id_t
ARDOUR::new_id ()
{
	return get_uid();
}

ARDOUR::Change
ARDOUR::new_change ()
{
	Change c;
	static uint32_t change_bit = 1;

	/* XXX catch out-of-range */

	c = Change (change_bit);
	change_bit <<= 1;

	return c;
}

static string
find_file (string name, string dir)
{
	string path;

	/* stop A: ~/.ardour/... */

	path = getenv ("HOME");

	if (path.length()) {
		
		path += "/.ardour/";

		/* try to ensure that the directory exists.
		   failure doesn't mean much here.
		*/

		mkdir (path.c_str(), 0755);

		path += name;

		if (access (path.c_str(), R_OK) == 0) {
			return path;
		}
	}

	/* stop B: dir/... */

	path = dir;
	path += name;
	
	if (access (path.c_str(), R_OK) == 0) {
		return path;
	}

	return "";
}

string
ARDOUR::find_config_file (string name)
{
	char* envvar;
	string dir;
	
	if ((envvar = getenv("ARDOUR_CONFIG_PATH")) == 0) {
		envvar = CONFIG_DIR;
		dir = envvar + string("/ardour/");
	}
	else {
		dir = envvar;
	}
	
	
	return find_file (name, dir);
}

string
ARDOUR::find_data_file (string name)
{
	char* envvar;
	string dir;

	if ((envvar = getenv("ARDOUR_DATA_PATH")) == 0) {
		envvar = DATA_DIR;
		dir = envvar + string("/ardour/");
	}
	else {
		dir = envvar;
	}
	
	return find_file (name, dir);
}

ARDOUR::LocaleGuard::LocaleGuard (const char* str)
{
	old = strdup (setlocale (LC_NUMERIC, NULL));
	if (strcmp (old, str)) {
		setlocale (LC_NUMERIC, str);
	} 
}

ARDOUR::LocaleGuard::~LocaleGuard ()
{
	setlocale (LC_NUMERIC, old);
	free ((char*)old);
}

ARDOUR::OverlapType
ARDOUR::coverage (jack_nframes_t sa, jack_nframes_t ea, 
		  jack_nframes_t sb, jack_nframes_t eb)
{
	/* OverlapType returned reflects how the second (B)
	   range overlaps the first (A).

	   The diagrams show various relative placements
	   of A and B for each OverlapType.

	   Notes:
	      Internal: the start points cannot coincide
	      External: the start and end points can coincide
	      Start: end points can coincide
	      End: start points can coincide

	   XXX Logically, Internal should disallow end
	   point equality.
	*/

	/*
	     |--------------------|   A
	          |------|            B
	        |-----------------|   B


             "B is internal to A"		

	*/
#ifdef OLD_COVERAGE
	if ((sb >= sa) && (eb <= ea)) {
#else
	if ((sb > sa) && (eb <= ea)) {
#endif
		return OverlapInternal;
	}

	/*
	     |--------------------|   A
	   ----|                      B
           -----------------------|   B
	   --|                        B
	   
	     "B overlaps the start of A"

	*/

	if ((eb >= sa) && (eb <= ea)) {
		return OverlapStart;
	}
	/* 
	     |---------------------|  A
                   |----------------- B
 	     |----------------------- B	   
                                   |- B

            "B overlaps the end of A"				   

	*/
	if ((sb >= sa) && (sb <= ea)) {
		return OverlapEnd;
	}
	/*
	     |--------------------|     A
	   --------------------------  B   
	     |-----------------------  B
	    ----------------------|    B
             |--------------------|    B


           "B overlaps all of A"
	*/
	if ((sa >= sb) && (sa <= eb) && (ea <= eb)) {
		return OverlapExternal;
	}

	return OverlapNone;
}

