require 'orientdb'
require_relative "database_utils.rb" #common methods without rest.specific content
require_relative "class_utils.rb" #common methods without rest.specific content
require_relative "orientdb_private.rb" 


module ActiveOrient


  class API
    include OrientSupport::Support
    include DatabaseUtils
    include ClassUtils
    include OrientDbPrivate
    include OrientDB

    mattr_accessor :logger # borrowed from active_support
    mattr_accessor :default_server
    attr_reader :database # Used to read the working database

    #### INITIALIZATION ####


    def initialize database: nil, connect: true, preallocate: true
      self.logger = Logger.new('/dev/stdout') unless logger.present?
      self.default_server = {
        :server => 'localhost',
        :port => 2480,
        :protocol => 'http',
        :user => 'root',
        :password => 'root',
        :database => 'temp'
      }.merge default_server.presence || {}
      @database = database || default_server[:database]
      @all_classes=[]
      #puts   ["remote:#{default_server[:server]}/#{@database}",
#					default_server[:user], default_server[:password] ]
      connect() if connect
#      @db  =   DocumentDatabase.connect("remote:#{default_server[:server]}/#{@database}",
#					default_server[:user], default_server[:password] )
      ActiveOrient::Model.api = self 
      preallocate_classes  if preallocate

    end

    def db
      @db
    end

# Used for the connection on the server
    #

# Used to connect to the database

    def connect

      @db  =   DocumentDatabase.connect("remote:#{default_server[:server]}/#{@database}",
					default_server[:user], default_server[:password] )
      @classes =  get_database_classes
    end

  

    def get_classes *attributes
      classes=  @db.metadata.schema.classes.map{|x| { 'name' => x.name , 'superClass' => x.get_super_class.nil? ? '': x.get_super_class.name } }
      unless attributes.empty?
	classes.map{|y| y.select{|v,_| attributes.include?(v)}}
      else
	classes
      end

    end


    def create_classes classes , &b
      consts = allocate_classes_in_ruby( classes , &b )

      all_classes = consts.is_a?( Array) ? consts.flatten : [consts]
      get_database_classes(requery: true)
      selected_classes =  all_classes.map do | this_class |
	this_class unless get_database_classes(requery: false).include?( this_class.ref_name ) rescue nil
      end.compact.uniq
      command= selected_classes.map do | database_class |
	## improper initialized ActiveOrient::Model-classes lack a ref_name class-variable
	next if database_class.ref_name.blank?  
	c = if database_class.superclass == ActiveOrient::Model || database_class.superclass.ref_name.blank?
	      puts "CREATE CLASS #{database_class.ref_name} "
	      OClassImpl.create @db, database_class.ref_name 
	    else
	      puts "CREATE CLASS #{database_class.ref_name} EXTENDS #{database_class.superclass.ref_name}"
	      OClassImpl.create @db, superClass: database_class.ref_name 
	    end
      end

      # update the internal class hierarchy 
      get_database_classes requery: true
      # return all allocated classes, no matter whether they had to be created in the DB or not.
      #  keep the format of the input-parameter
      consts.shift if block_given? && consts.is_a?( Array) # remove the first element
      # remove traces of superclass-allocations
      if classes.is_a? Hash
	consts =  Hash[ consts ] 
	consts.each_key{ |x| consts[x].delete_if{|y| y == x} if consts[x].is_a? Array  }
      end
      consts
    end



    def delete_class o_class
      @db.schema.drop_class classname(o_class)
      get_database_classes requery: true 
    end
=begin
  Creates properties and optional an associated index as defined  in the provided block
    create_properties(classname or class, properties as hash){index}

  The default-case
    create_properties(:my_high_sophisticated_database_class,
  		con_id: {type: :integer},
  		details: {type: :link, linked_class: 'Contracts'}) do
  		  contract_idx: :notunique
  		end

  A composite index
    create_properties(:my_high_sophisticated_database_class,
  		con_id: {type: :integer},
  		symbol: {type: :string}) do
  	    {name: 'indexname',
  			 on: [:con_id, :details]    # default: all specified properties
  			 type: :notunique            # default: :unique
  	    }
  		end
=end

    def create_properties o_class, **all_properties, &b
      logger.progname = 'JavaApi#CreateProperties'
      ap =  all_properties
      created_properties = ap.map do |property, specification | 
	puts "specification:  #{specification.inspect}"
	field_type = ( specification.is_a?( Hash) ?  specification[:type] : specification ).downcase.to_sym rescue :string
	the_other_class =  specification.is_a?(Hash) ?  specification[:other_class] : nil
	other_class = if the_other_class.present? 
			@db.get_class( the_other_class)
		      end
	index =  ap.is_a?(Hash) ?  ap[:index] : nil
	if other_class.present?
	  @db.get_class(classname(o_class)).add property,[ field_type, other_class ], { :index => index }
	else
	  @db.get_class(classname(o_class)).add property, field_type, { :index => index }
	end
      end
      if block_given?
	attr =  yield
        index_parameters = case attr 
	when String, Symbol
	  { name: attr }
	when Hash
	  { name: attr.keys.first , type: attr.values.first, on: all_properties.keys.map(&:to_s) }
	else
	  nil
	end
	create_index o_class, **index_parameters unless index_parameters.blank?
      end
      created_properties.size # return_value

      end

    def get_properties o_class
      @db.get_class(classname(o_class)).propertiesMap
    end



    def create_index o_class, name:, on: :automatic, type: :unique
      logger.progname = 'JavaApi#CreateIndex'
      begin
	c = @db.get_class( classname( o_class ))
	index = if on == :automatic
		  nil   # not implemented
		elsif on.is_a? Array
		  c.createIndex name.to_s, INDEX_TYPES[type],  *on
		else
		  c.createIndex name.to_s, INDEX_TYPES[type],  on
		end
      end
    end

  def create_record o_class, attributes: {}
    logger.progname = 'HavaApi#CreateRecord'
    attributes = yield if attributes.empty? && block_given?
    new_record = insert_document( o_class, attributes.to_orient )


  end
  alias create_document create_record

  def insert_document o_class, attributes
    d =  Document.create @db, classname(o_class), **attributes
    d.save
    ActiveOrient::Model.get_model_class(classname(o_class)).new attributes.merge( { "@rid" => d.rid,
										  "@version" => d.version,
										  "@type" => 'd',
										  "@class" => classname(o_class) } )



  end
  end
end