require 'net/http'
require 'openssl'
require 'yaml'
require 'open-uri'
require_relative 'filelogger'

# class representing a Wendelin client
class WendelinClient

  def initialize(erp5_url, user, password)
    @erp5_url = erp5_url
    @user = user
    @password = password
    @banned_references_list = []
    @logger = LogManager.instance()
    @last_ingestion = Time.new - 2
  end

  def checkReferenceChars(reference)
    if ["&", ";", "#", "%", '"', "+"].any? { |char| reference.include?(char) }
      raise "invalid char in filename. Following chars are not allowed for filenames: \& \; \% \" \+ \# Please rename it."
    end
  end

  def exists(reference)
    checkReferenceChars(reference)
    uri = URI(URI.escape("#{@erp5_url}/ingestionReferenceExists?reference=#{reference}"))
    begin
      res = open(uri, http_basic_authentication: [@user, @password]).read
    rescue Exception => e
      @logger.error("An error occurred while checking if reference exists: " + e.to_s)
      @logger.error(e.backtrace)
      if e.to_s.include? "Unauthorized" or e.to_s.include? "401"
	raise e
      end
      return FALSE
    else
      return res.to_s == 'TRUE'
    end
  end

  def delete(reference)
    @logger.info("Deletion requested for reference #{reference}", print=TRUE)
    checkReferenceChars(reference)
    uri = URI(URI.escape("#{@erp5_url}/ERP5Site_invalidateIngestionObjects?reference=#{reference}"))
    res = handleRequest(uri)
    if res == FALSE
      @logger.abortExecution()
    end
    @logger.info("Remote file successfully ingested.", print=TRUE)
  end

  def rename(reference, new_reference)
    @logger.info("Rename requested for reference #{reference}, new reference #{new_reference}", print=TRUE)
    checkReferenceChars(reference)
    checkReferenceChars(new_reference)
    uri = URI(URI.escape("#{@erp5_url}/ERP5Site_renameIngestion?reference=#{reference}&new_reference=#{new_reference}"))
    res = handleRequest(uri)
    if res == FALSE
      @logger.abortExecution()
    end
    @logger.info("Remote file successfully renamed.", print=TRUE)
  end

  def increaseDatasetVersion(reference)
    if reference == ""
      @logger.warn("Could not increase data set version because dataset reference is empty.")
    else
      @logger.info("Increasing dataset version")
      uri = URI(URI.escape("#{@erp5_url}/ERP5Site_increaseDatasetVersion?reference=#{reference}"))
      begin
	res = open(uri, http_basic_authentication: [@user, @password]).read
      rescue Exception => e
	@logger.error("An error occurred while increasing dataset version: " + e.to_s)
	@logger.error(e.backtrace)
      end
    end
  end

  def ingest(reference, data_chunk, split)
      @logger.info("Ingestion reference: #{reference}", print=TRUE)
      if split and Time.new - @last_ingestion < 3
	# avoid to send split ingestions to close
	sleep 3
      end
      if exists(reference)
	@logger.info("There is another ingestion already done for the pair dataset-filename. Reference "\
	      + reference, print=TRUE)
	@logger.info("Rename your file or download the full dataset to make local changes.", print=TRUE)
	return FALSE
      end
      checkReferenceChars(reference)
      uri = URI(URI.escape("#{@erp5_url}/ingest?reference=#{reference}"))
      response = handleRequest(uri, reference, data_chunk)
      if response == FALSE
	return FALSE
      end
      @logger.info("Record successfully ingested.", print=TRUE)
      @last_ingestion = Time.new
      return TRUE
  end

  def eachDataStreamContentChunk(id, chunk_size)
    uri = URI(URI.escape("#{@erp5_url}#{id}/getData"))
    @logger.info("Downloading...", print=TRUE)
    first = TRUE
    res = open(uri, http_basic_authentication: [@user, @password])  { 
      |content| 
	while true
	  chunk = content.read(chunk_size)
	  if chunk.nil?
	    if first
	      yield chunk
	    end
	    @logger.info("Done", print=TRUE)
	    break
	  end
	  first = FALSE
	  yield chunk
	end
     }
   end

  def getDataStreams(data_set_reference)
    uri = URI(URI.escape("#{@erp5_url}getDataStreamList?data_set_reference=#{data_set_reference}"))
    str = handleRequest(uri)
    if str == FALSE
      @logger.abortExecution()
    end
    if not str.nil?
	str.gsub!(/(\,)(\S)/, "\\1 \\2")
    	return YAML::load(str)
    end
    return {'status_code': 0, 'result': []}
  end

  private
  def handleRequest(uri, reference=nil, data_chunk=nil)
      req = Net::HTTP::Post.new(uri)
      req.basic_auth @user, @password

      if data_chunk != nil
	@logger.info("Setting request form data...", print=TRUE)
	begin
	  req.set_form_data('data_chunk' => data_chunk)
	rescue java.lang.OutOfMemoryError
	  @logger.logOutOfMemoryError(reference)
	  return FALSE
	end
	@logger.info("Sending record:'#{reference}'...", print=TRUE)
      end

      begin
	  res = Net::HTTP.start(uri.hostname, uri.port,
		  :use_ssl      => (uri.scheme == 'https'),
		  :verify_mode  => OpenSSL::SSL::VERIFY_NONE,
		  :ssl_timeout  => 300, :open_timeout => 300, :read_timeout => 300,
		) do |http|
		  http.request(req)
		end
      rescue Exception => e  
	  @logger.error("HTTP ERROR: " + e.to_s, print=TRUE)
	  @logger.error(e.backtrace)
	  return FALSE
      else
	  if res.kind_of?(Net::HTTPSuccess) # res.code is 2XX
	      @logger.info("Done")
	      return res.body
	  else
	      @logger.error("HTTP FAIL -  code: #{res.code}", print=TRUE)
	      if res.code == '500' or res.code == '502' or res.code == '503'
		@logger.error("Internal Server Error: if the error persists, please contact the administrator.", print=TRUE)
	      elsif res.code == '401'
		@logger.error("Unauthorized access. Please check your user credentials and try again.", print=TRUE)
		@logger.abortExecution()
	      else
		@logger.error("Sorry, an error ocurred. If the error persists, please contact the administrator.", print=TRUE)
	      end
	      return FALSE
	  end
      end 
  end
end
