# -*- ruby -*-
#encoding: utf-8

require 'arborist' unless defined?( Arborist )
require 'msgpack'


# Unified Arborist Manager client for both the Tree and Event APIs
class Arborist::Client
	extend Loggability

	# The version of the client.
	API_VERSION = 1

	# Loggability API -- log to the Arborist log host
	log_to :arborist


	### Create a new Client with the given API socket URIs.
	def initialize( tree_api_url: nil, event_api_url: nil )
		@tree_api_url  = tree_api_url  || Arborist.tree_api_url
		@event_api_url = event_api_url || Arborist.event_api_url

		@request_queue = nil
		@event_subscriptions = nil
	end


	######
	public
	######

	# The ZMQ URI required to speak to the Arborist tree API.
	attr_accessor :tree_api_url

	# The ZMQ URI required to speak to the Arborist event API.
	attr_accessor :event_api_url


	### Return the manager's current status as a hash.
	def status
		request = self.make_status_request
		return self.send_tree_api_request( request )
	end


	### Return the manager's current status as a hash.
	def make_status_request
		return self.pack_message( :status )
	end


	### Return the manager's current node tree.
	def list( *args )
		request = self.make_list_request( *args )
		return self.send_tree_api_request( request )
	end


	### Return the manager's current node tree.
	def make_list_request( from: nil )
		header = {}
		self.log.debug "From is: %p" % [ from ]
		header[:from] = from if from

		return self.pack_message( :list, header )
	end


	### Return the manager's current node tree.
	def fetch( criteria={}, *args )
		request = self.make_fetch_request( criteria, *args )
		return self.send_tree_api_request( request )
	end


	### Return the manager's current node tree.
	def make_fetch_request( criteria, include_down: false, properties: :all )
		header = {}
		header[ :include_down ] = true if include_down
		header[ :return ] = properties if properties != :all

		return self.pack_message( :fetch, header, criteria )
	end


	### Update the identified nodes in the manager with the specified data.
	def update( *args )
		request = self.make_update_request( *args )
		return self.send_tree_api_request( request )
	end


	### Update the identified nodes in the manager with the specified data.
	def make_update_request( data )
		return self.pack_message( :update, nil, data )
	end


	### Add a subscription
	def subscribe( *args )
		request = self.make_subscribe_request( *args )
		response = self.send_tree_api_request( request )
		return response.first
	end


	### Make a subscription request for the specified +criteria+, +identifier+, and +event_type+.
	def make_subscribe_request( criteria: {}, identifier: nil, event_type: nil )
		self.log.debug "Making subscription request for identifier: %p, event_type: %p, criteria: %p" %
			[ identifier, event_type, criteria ]
		header = {}
		header[ :identifier ] = identifier if identifier
		header[ :event_type ] = event_type

		return self.pack_message( :subscribe, header, criteria )
	end


	### Remove a subscription
	def unsubscribe( *args )
		request = self.make_unsubscribe_request( *args )
		response = self.send_tree_api_request( request )
		return response
	end


	### Remove the subscription with the specified +subid+.
	def make_unsubscribe_request( subid )
		self.log.debug "Making unsubscribe request for subid: %s" % [ subid ]

		return self.pack_message( :unsubscribe, subscription_id: subid )
	end


	### Send the packed +request+ via the Tree API socket, raise an error on
	### unsuccessful response, and return the response body.
	def send_tree_api_request( request )
		self.log.debug "Sending request: %p" % [ request ]
		self.tree_api.send( request )

		res = self.tree_api.recv
		self.log.debug "Received response: %p" % [ res ]

		header, body = self.unpack_message( res )
		unless header[ 'success' ]
			raise "Arborist manager said: %s" % [ header['reason'] ]
		end

		return body
	end



	### Format ruby +data+ for communicating with the Arborist manager.
	def pack_message( verb, *data )
		header = data.shift || {}
		body   = data.shift

		header.merge!( action: verb, version: API_VERSION )

		self.log.debug "Packing message; header: %p, body: %p" % [ header, body ]

		return MessagePack.pack([ header, body ])
	end


	### De-serialize an Arborist manager response.
	def unpack_message( msg )
		return MessagePack.unpack( msg )
	end


	### Return a ZMQ REQ socket connected to the manager's tree API, instantiating
	### it if necessary.
	def tree_api
		return @tree_api ||= self.make_tree_api_socket
	end


	### Create a new ZMQ REQ socket connected to the manager's tree API.
	def make_tree_api_socket
		self.log.info "Connecting to the tree socket %p" % [ self.tree_api_url ]
		sock = Arborist.zmq_context.socket( :REQ )
		sock.connect( self.tree_api_url )

		return sock
	end


	### Return a ZMQ SUB socket connected to the manager's event API, instantiating
	### it if necessary.
	def event_api
		return @event_api ||= self.make_event_api_socket
	end


	### Create a new ZMQ SUB socket connected to the manager's event API.
	def make_event_api_socket
		self.log.info "Connecting to the event socket %p" % [ self.event_api_url ]
		sock = Arborist.zmq_context.socket( :SUB )
		sock.connect( self.event_api_url )

		return sock
	end

end # class Arborist::Client