require 'yaml'
require 'shellwords'

module Sfp::Module
end

###############
#
# Module Sfp::Resource must be included by every module. It provides
# standard methods which are used by Runtime engine in mapping between
# SFP object and schema implementation.
#
# accessible attributes
# - parent        : holds instance of parent's object
#
# - synchronized  : an list of SFP procedures that must be executed in
#                   serial
#
# - path          : an absolute path of this instance
#
# read-only attributes
# - state         : holds the current state of this module instance
#
# - model         : holds the model of desired state of this module
#                   instance
#
# methods:
# - init          : invoked by Runtime engine after instantiating this
#                   module instance for initialization
#
# - update_state  : invoked by Runtime engine to request this module
#                   instance to update the current state which should
#                   be kept in attribute @state
#
# - to_model      : can be invoked by this module instance to set the
#                   current state equals the desired state (model), or
#                   in short: @state == @model
#
# - resolve_state : can be invoked by this module to resolve given
#                   reference of current state either local or other
#                   module instances
#
# - resolve       : an alias to method resolve_state
#
# - resolve_model : can be invoked by this module to resolve given
#                   reference of desired state (model) either local or
#                   other module instances
#
# - log           : return logger object
#
# - copy          : copy a file, whose path is the first parameter, to
#                   a destination path given in the second parameter
#
# - render        : render given template file, whose path is the first
#                   parameter, and the template's variable is a merged
#                   between a Hash in the second parameter with model;
#                   the result is returned as a string
#
# - render_file   : render given template file, whose path is the first
#                   parameter, and the template's variable is a merged
#                   between a Hash on the second parameter with model;
#                   the result is written back to the file
#
###############
module Sfp::Resource
	@@resource = Object.new.extend(Sfp::Resource)

	attr_accessor :parent, :synchronized, :path
	attr_reader :state, :model

	def init(model={})
		@state = {}
		@model = (model.length <= 0 ? {} : Sfp.to_ruby(model))
		@synchronized = []
	end

	def update_state
		@state = {}
	end

	##############################
	#
	# Helper methods for resource module
	#
	##############################

	def self.resolve(path)
		@@resource.resolve(path)
	end

	protected
	def to_model
		@state = {}
		@model.each { |k,v| @state[k] = v }
	end

	def resolve_state(path)
		Sfp::Agent.resolve(path)
	end

	alias_method :resolve, :resolve_state

	def resolve_model(path)
		Sfp::Agent.resolve_model(path)
	end

	def log
		Sfp::Agent.logger
	end

	def shell(cmd)
		!!system(cmd)
	end

	def copy(source, destination)
		shell "cp -rf #{source} #{destination}"
	end

	def render(string, map={})
		model = @model.clone
		map.each { |k,v| model[k] = v }
		::Sfp::Template.render(string, model)
	end

	def render_file(file, map={})
		model = @model.clone
		map.each { |k,v| model[k] = v }
		::Sfp::Template.render_file(file, model)
	end

	def download(source, destination)
		def use_http_proxy?(uri)
			ENV['no_proxy'].to_s.split(',').each { |pattern|
				pattern.chop! if pattern[-1] == '*'
				return false if uri.host[0, pattern.length] == pattern
			}
			true
		end

		file = nil
		begin
			uri = URI.parse(source)
			http = nil
			if use_http_proxy?(uri) and ENV['http_proxy'].to_s.strip.length > 0
				begin
					proxy = URI.parse(ENV['http_proxy'])
					http = Net::HTTP::Proxy(proxy.host, proxy.port).new(uri.host, uri.port)
				rescue Exception => e
					log.info "Invalid http_proxy=#{ENV['http_proxy']}"
					http = Net::HTTP.new(uri.host, uri.port)
				end
			else
				http = Net::HTTP.new(uri.host, uri.port)
			end
			http.request_get(uri.path) do |response|
				file = ::File.open(destination, 'wb')
				response.read_body do |segment|
					file.write segment
				end
				file.flush
			end
		ensure
			file.close if not file.nil?
		end
	end
end

class Sfp::Module::Shell
	include Sfp::Resource

	attr_reader :home, :main

	def initialize(metadata)
		### set module's home directory
		@home = metadata[:home]

		### set main shell command
		@main = @home + '/main'
	end

	def update_state
		@state = invoke({
			:command => :state,
			:model => @model,
			:path => @path
		})
	end

	def execute(name, parameters={})
		result = invoke({
			:command => :execute,
			:procedure => name.split('.').last,
			:parameters => parameters,
			:model => @model,
			:path => @path
		})
		if result['status'] != 'ok'
			log.error "Error in executing #{name} - description: #{result['description']}"
			false
		else
			true
		end
	end

	private

	def invoke(parameters)
		log.info Shellwords.shellescape(JSON.generate(parameters))
		begin
			output = `#{@main} #{Shellwords.shellescape(JSON.generate(parameters))}`
			log.info output
			JSON.parse(output)
		rescue Exception => exp
			log.info "Invalid module output: #{output}"
			raise exp
		end
	end
end