# coding: utf-8

require 'rubygems'
require 'persistent_httparty'
require 'json'
require 'rspec'
require 'rspec/expectations'


$address = ENV['ARANGO_SERVER'] || '127.0.0.1:8529'
$user = ENV['ARANGO_USER']
$password = ENV['ARANGO_PASSWORD']
$ssl = ENV['ARANGO_SSL']
$silent = ENV['ARANGO_NO_LOG'] || ''
$skip_timecritical = ENV['SKIP_TIMECRITICAL'] || true

begin
  $address = RSpec.configuration.ARANGO_SERVER
rescue
end
begin
  $user = RSpec.configuration.ARANGO_USER
rescue
end
begin
  $password = RSpec.configuration.ARANGO_PASSWORD
rescue
end
begin
  $ssl = RSpec.configuration.ARANGO_SSL
rescue
end
begin
  $skip_timecritical = RSpec.configuration.SKIP_TIMECRITICAL
rescue
end

RSpec.configure do |config|
  config.expect_with :rspec do |c|
    c.syntax = [ :should, :expect ]
  end
  config.mock_with :rspec do |c|
    c.syntax = [ :should, :expect ]
  end
  # tests to be excluded when under ssl
  if $ssl == '1'
    config.filter_run_excluding :ssl => true
  end
  if $skip_timecritical
    config.filter_run_excluding :timecritical => true
  end
end

################################################################################
## cross rspec version compatibility tool: 
################################################################################
RSpec::Matchers.define :be_nil_or_empty do
 match do |actual|
   actual.nil? || actual.empty?
 end
end

class ArangoDB
  include HTTParty
  persistent_connection_adapter

  if $ssl == '1'
    base_uri "https://#{$address}"
  else
    base_uri "http://#{$address}"
  end 

  # set HTTP basic authorization
  basic_auth $user, $password

  # expect json as output/response format
  format :json

  # set timeout to 300 seconds - so we will see timeouts in the cluster with ther
  # respective error messages from the coordinator.
  default_timeout 300 

  # do not verify SSL
  default_options[:verify] = false

################################################################################
## adjust the global client-side timeout value
################################################################################

  def self.set_timeout (value) 
    old_value = default_options[:timeout] 
    default_options[:timeout] = value
    old_value
  end

  def self.set_read_timeout (value) 
    old_value = default_options[:read_timeout] 
    default_options[:read_timeout] = value
    old_value
  end
  
################################################################################
## create a database
################################################################################

  def self.create_database (name)
    body = "{ \"name\" : \"#{name}\" }"

    doc = self.post("/_api/database", :body => body)

    if doc.code != 201
      return false
    end

    return true
  end

################################################################################
## drop a database
################################################################################

  def self.drop_database (name)
    body = ""

    doc = self.delete("/_api/database/#{name}", :body => body)

    if doc.code != 200
      return false
    end

    return true
  end

################################################################################
## create a collection
################################################################################

  def self.create_collection (name, wait_for_sync = true, type = 2, dbname = "_system")
    # type 2 means "document collection"
    body = "{ \"name\" : \"#{name}\", \"waitForSync\" : #{wait_for_sync}, \"type\" : #{type} }"

    doc = self.post("/_db/#{dbname}/_api/collection", :body => body)

    if doc.code == 409
      return doc.parsed_response['id']
    end

    if doc.code != 200
      return nil
    end

    return doc.parsed_response['id']
  end

################################################################################
## drop a collection
################################################################################

  def self.drop_collection (name, dbname = "_system")
    cmd = "/_db/#{dbname}/_api/collection/#{name}"
    self.delete(cmd)
  end

################################################################################
## size of a collection
################################################################################

  def self.size_collection (name)
    doc = self.get("/_api/collection/#{name}/count") # TODO use api call

    return doc.parsed_response['count']
  end

################################################################################
## create a single collection graph
################################################################################

  def self.create_single_collection_graph (name, edgeCollection, vertexCollection)
  edge_definitions = {:collection => edgeCollection, :from => [vertexCollection], :to => [vertexCollection]}
  body = JSON.dump({:name => name, :edgeDefinitions => [edge_definitions]})

    doc = self.post("/_api/gharial", :body => body)

    return doc
  end

################################################################################
## drop a graph
################################################################################

  def self.drop_graph (name)
    cmd = "/_api/gharial/#{name}?dropCollections=true"
    self.delete(cmd)
  end

################################################################################
## issues a get request
################################################################################

  def self.log_get (output, url, args = {})
    doc = self.get(url, args);
    self.log(:method => :get, :url => url, :body => args[:body], :headers => args[:headers], :result => doc, :output => output, :format => args[:format]);
    return doc
  end

################################################################################
## issues a head request
################################################################################

  def self.log_head (output, url, args = {})
    doc = self.head(url, args);
    self.log(:method => :head, :url => url, :body => args[:body], :headers => args[:headers], :result => doc, :output => output, :format => args[:format]);
    return doc
  end

################################################################################
## issues an options request
################################################################################

  def self.log_options (output, url, args = {})
    doc = self.options(url, args);
    self.log(:method => :options, :url => url, :body => args[:body], :headers => args[:headers], :result => doc, :output => output, :format => args[:format]);
    return doc
  end

################################################################################
## issues a post request
################################################################################

  def self.log_post (output, url, args = {})
    doc = self.post(url, args);
    self.log(:method => :post, :url => url, :body => args[:body], :headers => args[:headers], :result => doc, :output => output, :format => args[:format]);
    return doc
  end

################################################################################
## issues a put request
################################################################################

  def self.log_put (output, url, args = {})
    doc = self.put(url, args);
    self.log(:method => :put, :url => url, :body => args[:body], :headers => args[:headers], :result => doc, :output => output, :format => args[:format]);
    return doc
  end

################################################################################
## issues a delete request
################################################################################

  def self.log_delete (output, url, args = {})
    doc = self.delete(url, args);
    self.log(:method => :delete, :url => url, :body => args[:body], :headers => args[:headers], :result => doc, :output => output, :format => args[:format]);
    return doc
  end

################################################################################
## issues a patch request
################################################################################

  def self.log_patch (output, url, args = {})
    doc = self.patch(url, args);
    self.log(:method => :patch, :url => url, :body => args[:body], :headers => args[:headers], :result => doc, :output => output, :format => args[:format]);
    return doc
  end

################################################################################
## generate log file
################################################################################

  def self.log (args)
    # disable logging if requested
    result = args[:result]
    response = result.parsed_response
    return if not ($silent.nil? || $silent.empty? || $silent == "0")

    if args.key?(:output)
      logfile = File.new("logs/#{args[:output]}", "a")
    else
      logfile = File.new("output.log", "a")
    end

    method = args[:method] || :get
    url = args[:url]
    body = args[:body]
    headers = args[:headers]
    result = args[:result]
    response = result.parsed_response

    logfile.puts '-' * 80

    h_option = ""
    h_sep = ""

    if headers
      for k in headers.keys do
        h_option = h_option + h_sep + "'-H #{k}: #{headers[k]}'"
        h_sep = " "
      end
      h_option = h_option + h_sep
    end

    if method == :get
      logfile.puts "> curl -X GET #{h_option}--dump - http://localhost:8529#{url}"
      logfile.puts
    elsif method == :head
      logfile.puts "> curl -X HEAD #{h_option}--dump - http://localhost:8529#{url}"
      logfile.puts
    elsif method == :delete
      logfile.puts "> curl -X DELETE #{h_option}--dump - http://localhost:8529#{url}"
      logfile.puts
    elsif method == :post
      if body == nil
        logfile.puts "> curl -X POST #{h_option}--dump - http://localhost:8529#{url}"
      else
        logfile.puts "> curl --data @- -X POST #{h_option}--dump - http://localhost:8529#{url}"
        logfile.puts body
      end
      logfile.puts
    elsif method == :put
      if body == nil
        logfile.puts "> curl -X PUT #{h_option}--dump - http://localhost:8529#{url}"
      else
        logfile.puts "> curl --data @- -X PUT #{h_option}--dump - http://localhost:8529#{url}"
        logfile.puts body
      end
      logfile.puts
    elsif method == :patch
      if body == nil
        logfile.puts "> curl -X PATCH #{h_option}--dump - http://localhost:8529#{url}"
      else
        logfile.puts "> curl --data @- -X PATCH #{h_option}--dump - http://localhost:8529#{url}"
        logfile.puts body
      end
      logfile.puts
    else
      logfile.puts "MISSING"
    end

    logfile.puts "HTTP/1.1 #{result.code} #{result.message}"

    if result.headers.key?('content-type')
      logfile.puts "content-type: #{result.headers['content-type']}"
    end

    if result.headers.key?('location')
      logfile.puts "location: #{result.headers['location']}"
    end

    if result.headers.key?('etag')
      logfile.puts "etag: #{result.headers['etag']}"
    end

    if result.headers.key?('x-arango-async-id')
      logfile.puts "x-arango-async-id: #{result.headers['x-arango-async-id']}"
    end

    if response != nil
      format = "json"
      if args[:format] != nil
        format = args[:format]
      end

      if format == "json" 
        logfile.puts
        logfile.puts JSON.pretty_generate(response)
      end
    end

    logfile.close
  end
end

# super carrot fix for strange SSL behaviour on windows
# on some windowses the first SSL request somehow throws strange SSL errors
# after that everything is fine...so make a dummy request first
if $ssl == '1'
  retries = 0
  begin
    ArangoDB.get("/_api/version")
  rescue
    retries+=1
    if retries < 5 then
      retry
    else
      raise
    end
  end
end
