#!/usr/bin/env ruby

require 'Qt4'

$DEBUG = true
$VERBOSE = true

require 'pp'
require 'set'
require 'fcntl'
require 'pathname'

$:.push "lib"

require 'qasim'
require 'qasim/qasim_qrc'

# QaSiM // Qt Sshfs Mapper 

def _ str
	Qt::Object.tr(str)
end


module Qasim
	class QasimApp
		def initialize
		end
	end

	class QasimGui < QasimApp

		class LockError < RuntimeError ; end

		def initialize
			@config = Config.new
			#@config.parse_cmd_line ARGV

			@map_menu = nil
			@context_menu = nil
			@connect_error = {}
			@connect_running = {}
		end

		def dbus_notify title, body, icon
			bus = Qt::DBusConnection.sessionBus
			if !bus.connected?
				$stderr.puts(
          "Cannot connect to the D-BUS session bus.\n" \
					"To start it, run:\n" \
					"\teval `dbus-launch --auto-syntax`\n"
        )
				exit 1
			end
			msg = Qt::DBusMessage.create_method_call(
        'org.freedesktop.Notifications',
				'/org/freedesktop/Notifications',
				'org.freedesktop.Notifications',
				'Notify' 
      )
			msg.arguments = [ APP_NAME, Qt::Variant.from_value( 0, "unsigned int" ),
				             icon, title, body, [], {}, -1  ]
			rep = bus.call( msg )
			#	if rep.type == Qt::DBusMessage

			#		si.showMessage("Qasim", 
			#					   "Sorry dude", 2, 5000 )
		end


		#
		# Rebuild map menu
		#
		def build_map_menu
			# reload maps dynamically
			@config.parse_maps
			if @map_menu.nil? then
				@map_menu = Qt::Menu.new
			else 
				@map_menu.clear
			end

			previous_host = nil
			@config.maps.sort do |mx,my|
				mx.host <=> my.host
			end.each do |map|
				if map.host != previous_host and not previous_host.nil? then
					@map_menu.addSeparator
				end
				itemx = Qt::Action.new(map.name, @map_menu)
				itemx.setCheckable true;
				if map.connected? then
					itemx.setChecked true
				end
				itemx.connect(SIGNAL(:triggered)) do 
					action_trigger_map_item map, itemx
				end
				@map_menu.addAction itemx;
				previous_host = map.host
			end
		end

		#
		# Action when map item triggered
		#
		def action_trigger_map_item map, item
			@connect_error[map.path] = Set.new
			@connect_running[map.path] = 0
			method = if map.connected? then :disconnect
					     else :connect
					     end

			begin
				map.send(method) do |linkname,cmd,cmd_args|
					process = Qt::Process.new
					process.connect(SIGNAL('finished(int, QProcess::ExitStatus)')) do |exitcode,exitstatus|
						#puts "exitcode = %s, exitstatus = %s" % [exitcode, exitstatus]
						@connect_running[map.path] -= 1 
						if exitcode != 0 then
							@connect_error[map.path].add linkname
						else
						end
						if @connect_running[map.path] == 0 then
							# display someting
							if @connect_error[map.path].empty? then

								dbus_notify "%s (%s)" % [APP_NAME, map.name], 
									("<b>Map %sed successfully<b>" % method.to_s), 
									'dialog-information'
							else
								erroneous = @connect_error[map.path].to_a.join(', ')
								dbus_notify "%s (%s)" % [APP_NAME, map.name], 
									("<b>Unable to %s map</b><br>" % method.to_s) +
									("Broken link(s): %s" % erroneous), 
									'dialog-error'
							end
						end
					end
					@connect_running[map.path] += 1
					process.start cmd, cmd_args
				end

			rescue Map::ConnectError => e
				puts e.inspect
			end
		end

		#
		#
		#
		def build_context_menu
			@context_menu = Qt::Menu.new

			act_pref = Qt::Action.new _('&Preferences'), @context_menu
			act_pref.setIcon(  Qt::Icon::fromTheme("configure") ) rescue nil
			act_pref.setIconVisibleInMenu true
			act_pref.setEnabled false
			act_pref.connect(SIGNAL(:triggered)) do 
				res = @pref_dialog.show
			end
			@context_menu.addAction act_pref;

			act_about = Qt::Action.new '&About', @context_menu
			act_about.setIcon( Qt::Icon::fromTheme("help-about") ) rescue nil
			act_about.setIconVisibleInMenu true
			#act_about.setEnabled true
			act_about.connect(SIGNAL(:triggered)) do 
				res = @about_dialog.show
			end
			@context_menu.addAction act_about;

			@context_menu.addSeparator

			act_quit = Qt::Action.new _('Quit'), @context_menu
			act_quit.setIcon(  Qt::Icon::fromTheme("application-exit") ) rescue nil
			act_quit.setIconVisibleInMenu true
			act_quit.connect(SIGNAL(:triggered)) { @app.quit }
			@context_menu.addAction act_quit
		end


		#
		#
		#
		def build_interface

			@app = Qt::Application.new(ARGV)
			#Qt.debug_level = Qt::DebugLevel::High
			#Qt.debug_level = Qt::DebugLevel::Extensive
			@app.setQuitOnLastWindowClosed false

			@main_win = Qt::MainWindow.new
			@systray  = Qt::SystemTrayIcon.new @main_win
			@about_dialog = Qasim::Ui::About.new @main_win
			@pref_dialog = Qasim::Ui::Preferences.new @main_win

			std_icon = Qt::Icon.new( ":/qasim/qasim-icon" )
			alt_icon = Qt::Icon.new
			blinking = false

			@systray.icon  = std_icon
			@systray.show


			@systray.setToolTip("Qasim %s" % APP_VERSION);

			build_map_menu
			build_context_menu

			@systray.contextMenu = @context_menu

			@systray.connect(SIGNAL('activated(QSystemTrayIcon::ActivationReason)')) do |reason|
				case reason
				when Qt::SystemTrayIcon::Trigger then
					build_map_menu
					@map_menu.popup(Qt::Cursor::pos())
					#blinking = !blinking
					#si.icon  = blinking ? alt_icon : std_icon
				when Qt::SystemTrayIcon::MiddleClick then
					#
				when Qt::SystemTrayIcon::Context then
					#
				when Qt::SystemTrayIcon::DoubleClick then
					#
				end
			end
		end


		def lock_set
			begin
				# create an exclusive lock file
				have_lock = true

				FileUtils.mkdir_p APP_CONFIG_DIR unless File.exist? APP_CONFIG_DIR
				lockfname = File.join APP_CONFIG_DIR, "lock"
				fd = IO::sysopen( lockfname,
								         Fcntl::O_WRONLY | Fcntl::O_EXCL | Fcntl::O_CREAT)
				f = IO.open(fd)
				f.syswrite( "#{Process.pid}\n" )
				f.close
			rescue Errno::EEXIST => e
				# test if the other process still exist
				masterpid = File.read(lockfname).strip
				other_path = "/proc/#{masterpid.to_i}"
				STDERR.puts "testing %s" % other_path
				if File.exist? other_path then
					cmdline = File.read( File.join( other_path, 'cmdline' ) )
					if cmdline =~ /qasim/ then
						raise LockError, "Another instance of %s is already running." % APP_NAME
					end
				end
				fd = IO::sysopen( lockfname,
								         Fcntl::O_WRONLY | Fcntl::O_EXCL )
				f = IO.open(fd)
				f.syswrite( "#{Process.pid}\n" )
				f.close
			end
		end

		def lock_unset
			# remove lock if it exists
			lockfname = File.join APP_CONFIG_DIR, "lock"
			return unless File.exist? lockfname
			masterpid = File.read(lockfname).strip
			if masterpid.to_i == Process.pid then
				FileUtils.rm lockfname
			end
		end

		#
		#
		#
		def run
			Process.daemon(true) #FIXME: add foreground mode too
			lock_set
			@app.exec
			exit 0
		rescue LockError
			STDERR.puts "Error: %s is already running" % APP_NAME
			exit 1
		ensure
			lock_unset
		end


		#
		#
		#
		def self.main
			qasim = QasimGui.new
			qasim.build_interface
			qasim.run
		end

	end

end

Qasim::QasimGui::main