#!/usr/bin/env ruby

require 'open-uri'
require 'json'
require 'cgi'
require 'optparse' # ruby standard option parser

#--------------------------------class definitiom
class ScopusSearch
	attr_accessor :options, :version, :using_alfred, :scopusBaseURL
	VER = '1.0.10'.freeze
	OPT = Struct.new(:key, :doi, :uuid, :source, :openURLs, :append)
	
	#--------------------- constructor
	def initialize # set up class
		@options = OPT.new('7f59af901d2d86f78a1fd60c1bf9426a', 
								'', '', 'scopus', false, false)
		@version = VER
		@using_alfred = false
		@surl = ''
		@scite = ''
		@ncite = ''
		@uuid = ''
		@title = ''
		@subtitle = ''
		@cited = ''
		@success = false
		@scopusBaseURL = ''
	end

	# ###############################parse inputs
	def parseInputs(_arg)
		optparse = OptionParser.new do |opts|
			opts.banner = 'ScopusSearch V' + @version + "\n"
			opts.banner += "===================\n"
			opts.banner += " Either Pass the DOI with -d or select a ref in Bookends\n"
			opts.banner += " (containing a DOI), and get the Scopus URLs back…\n"
			opts.on('-k', '--key KEY', 'API KEY?') do |v|
				@options[:key] = v
			end
			opts.on('-d', '--doi DOI', 'DOI to search for?') do |v|
				@options[:doi] = v
			end
			opts.on('-s', '--source database', String, 'scopus (default) or scidir?') do |v|
				@options[:source] = v
			end
			opts.on('-a', '--append [true]|false', TrueClass, 'append URLS to Bookends note') do |v|
				@options[:append] = v
			end
			opts.on('-o', '--open true|[false] ', FalseClass, 'open URLs in browser?') do |v|
				@options[:openURLs] = v
			end
			opts.on('-h', '--help', 'Prints this help!') do
				puts optparse
				exit(0)
			end
		end # end OptionParser
		optparse.parse!
	end # end parseInputs

	def run
		returnNullResults('Incomplete input options...') if options.doi.nil? || options.key.nil?
		url = 'https://api.elsevier.com/content/search/' + options.source
		url += '?query=DOI%28%22' + CGI.escape(@options.doi) + '%22%29'
		url += '&apiKey=' + @options.key
		url += '&httpAccept=application%2Fjson'
		response = ''
		begin
			uri = URI.parse(url)
			response = uri.open(:proxy => nil).read
		rescue => exception
			if @using_alfred
				subt = "Fetching => #{@options.doi}"
				subt = "Try to disable system proxy. " + subt if exception.to_s =~ /127.0.0.1/
				jsono = { items: [{  uid: 'error', arg: '', title: 'URI error:'+exception.to_s, 
					subtitle: subt }] }
				puts JSON.pretty_generate(jsono)
			else
				puts 'Error fetching DOI: ' + exception.to_s
			end
			exit(false)
		end
		response, n, json = parseResponse(response)
		parseJSON(json) if n > 0
		if @success == true
			changeBaseURL
			returnResults
		else
			returnNullResults(response)
		end
	end

	def parseResponse(response)
		if response.nil? || response.empty?
			n = 0
			response = 'Response empty'
			json = ''
		elsif response =~ /service-error/
			n = 0
			response = 'Service Error'
			json = ''
		elsif response =~ /Result set was empty/
			n = 0
			response = 'Result set was empty'
			json = ''
		else
			json = JSON.parse(response)
			n = json['search-results']['opensearch:totalResults'].to_i
		end
		return response, n, json
	end

	def parseJSON(json)
		r = json['search-results']['entry']
		r.each do |re|
			@uuid = re['dc:identifier']
			@uuid = 'none' if @uuid.nil?
			@title = re['dc:title']
			@creator = re['dc:creator']
			@cited = re['citedby-count']
			@cited = '' if @cited.nil?
			links = re['link']
			links.each do |lnk|
				if lnk['@ref'] == options.source
					@surl = lnk['@href']
				elsif lnk['@ref'] == 'scopus-citedby'
					@scite = lnk['@href']
				elsif lnk['@ref'] == 'first'
					@scite = lnk['@href']
				end
			end
		end
		@success = true
	end

	def returnResults
		if @using_alfred
			jsonc = {
				uid: @uuid + "A", 
				arg: @scite, 
				title: "#{@cited} CITING: " + @title, 
				subtitle: 'Open URL to papers that cite this paper…'
			}
			if @surl.empty?
				jsono = { items: [jsonc]}
			else
				jsons = {  
					uid: @uuid + "B", 
					arg: @surl, 
					title: @title, 
					subtitle: "Open URL to the reference..."
				}
				jsono = { items: [jsonc, jsons]}
			end
			puts JSON.pretty_generate(jsono)
		else
			puts 'SCOPUS ' + @cited + ' CITED: ' + @scite + "\nSCOPUS ENTRY: " + @surl
		end
	end

	def returnNoDOI()
		if @using_alfred
			jsono = { items: [{  uid: 'error', arg: '-1', title: 'No DOI; please select ref with DOI!', 
				subtitle: 'Unique ID => ' + @options.uuid }] }
			puts JSON.pretty_generate(jsono)
		else
			puts 'No DOI was specified or found: ' + @options.uuid + ' - ' + response
		end
		exit(0)
	end

	def returnNullResults(response)
		if @using_alfred
			jsono = { items: [{  uid: 'error', arg: '-1', title: 'Sorry, no entry in Scopus was found', 
				subtitle: @options.doi + " => " + response }] }
			puts JSON.pretty_generate(jsono)
		else
			puts 'No results found for: ' + @options.doi + ' => ' + response
		end
		exit(0)
	end

	def writeNotes
		return unless @success == true
		return unless @options.append == true
		return if @options.uuid.empty?
		puts "Will try to write to ID=#{options.uuid}…" unless @using_alfred
		newnote = "#SCOPUS #{@cited} CITES: #{@scite}\r\r#SCOPUS URL: #{@surl}"
		osascript <<-EOT
		tell application "Bookends"
			tell front library window
				set tnote to notes of publication item id #{options.uuid}
				if tnote is not equal to "" then
					set tnote to tnote & return & return
				end if
				set out to tnote & "#{newnote}" & return & return
				set notes of publication item id #{options.uuid} to out
			end tell
		end tell
		EOT
		puts "… finished appending to: ID=#{options.uuid}" unless using_alfred
	end

	def getDOI
		results = osascript <<-EOT
		tell application "Bookends"
			set mynull to ASCII character 30
			set myRefs to selected publication items of front library window
			set thisID to id of first item of myRefs
			set thisDOI to doi of first item of myRefs
			return thisID & mynull & thisDOI
		end tell
		EOT
		if results.nil? || results.empty?
			@options[:uuid] = ''
			@options[:doi] = ''
		else
			results = results.split("\u001E") 
			@options[:uuid] = results[0].chomp.strip
			doi = results[1].chomp.strip
			doi = '' if doi.nil?
			@options[:doi] = doi
		end
	end

	def changeBaseURL
		return unless @success == true
		return if @scopusBaseURL.empty?
		@scite.gsub!(/https:\/\/[^\/]+\//,@scopusBaseURL)
		@surl.gsub!(/https:\/\/[^\/]+\//,@scopusBaseURL)
	end

	def open_links
		return unless @options.openURLs == true
		return unless @success == true
		puts 'Will open Scopus Links in browser...' unless using_alfred
		cmd = "open \"#{@surl}\""
		IO.popen(cmd)
		cmd = "open \"#{@scite}\""
		IO.popen(cmd)
	end

	# this converts to -e line format so osascript can run, pass in a heredoc
	# beware this splits on \n so can't include them in the applescript itself
	def osascript(script)
		cmd = ['osascript'] + script.split(/\n/).map { |line| ['-e', line] }.flatten
		IO.popen(cmd) { |f| return f.gets }
	end
end #--------------- end ScopusSearch class

#-------------------------------------------------
ss = ScopusSearch.new
ss.parseInputs(ARGV)

# check if running under alfred
ss.using_alfred = true unless ENV['alfred_version'].nil?

# check ENV for scopusKey, overrides default/ARGV
key = ENV['scopusKey']
ss.options.key = key unless key.nil? || key.empty?

# check ENV for append option, overrides ARGV
append = ENV['appendScopusNotes']
unless append.nil? || append.empty? || append.to_s =~ /(false|0)/
	ss.options.append = true
end

# check ENV for baseURL option
ss.scopusBaseURL = ENV['scopusBaseURL']
ss.scopusBaseURL = '' if ss.scopusBaseURL.nil?

# if no DOI try to get our DOI directly from the selected ref in Bookends
ss.getDOI if ss.options.doi.empty?

if ss.options.doi.empty?
	ss.returnNoDOI
else
	ss.run
	ss.writeNotes
	ss.open_links
	ENV['scopusSearchReturn'] = '0'
end
