module OrientSupport

	# This Module fences specialized Ruby objects

	# The Array _knows_ its database-class. This enables database-transactions outside the scope
	# of ActiveOrient
	#
	# The Database-Class is available through Array#record
#
# *caution:*
# Don't mix ActiveOrient::Array's with conventional ones
#   	> t= G21.first
#     > t.ll
#      => ["test", "test_2", 5, 8, 7988, "uzg"]
#   	> t.ll = [9,6,7]    # This is an assignment of an Array to the variable »ll»
#													# It does NOT call ActiveOrient::Array#=[]. 
#   	 => [9, 6, 7]       # Instead an Array is assigned to the variable  »ll»
#   
# it is only updated localy, as shown if we reload the document
#     > t= G21.first.attributes
#      => {:ll=>["test", "test_2", 5, 8, 7988, "uzg"]} 
#   
# Thus its imperativ to safe the changes made.
   
   
	class Array < Array
		include OrientSupport::Support

=begin
	During initialisation  the model-instance to work on is stored  in @orient.

	The keyword_parameter »work_on« holds the record to work on.
	The second argument holds the array to work with

	If instead of a model-instance the model-class is provided, a new model-instance is created and returned
	Its up to the caller to save the new instance in the database 

	Further a list of array-elements is expected, which are forwarded (as Array) to Array

	Its used to initialize Objects comming from the database (i.e. /lib/base.rb)

		 elsif iv.is_a? Array
					 OrientSupport::Array.new( work_on: self, work_with: iv.from_orient){ key.to_sym }

=end

		def initialize( work_on:, work_with: )
			@orient = work_on.class == Class ? work_on.new : work_on
			super work_with
			begin
			@name =  block_given? ? yield : @orient.attributes.key(self)
			rescue TypeError => e   #  not defined
				ActiveOrient::Base.logger.debug{ "--------------------Type Error ----------------------------------" }
			ActiveOrient::Base.logger.debug("OrientSupport::Array"){ "Attributes  #{@orient.attributes.inspect}" }
			ActiveOrient::Base.logger.debug("OrientSupport::Array"){ e.inspect 
			ActiveOrient::Base.logger.debug{ "indicates a try to access a non existing array element" }}
			nil
			rescue NameError =>e
			ActiveOrient::Base.logger.debug{ "--------------------Name Error ------------" }
			ActiveOrient::Base.logger.debug ("OrientSupport::Array"){ e.inspect }
			#ActiveOrient::Base.logger.error{ e.backtrace.map {|l| "  #{l}\n"}.join  }
			ActiveOrient::Base.logger.debug{ "due to a bug in ActiveSupport DateTime Calculations" }
			# we just ignore the error
			end
		end
		def as_json o=nil
			map{|x| x.rid? ? x.rid : x }
		end

		def record
			@orient
		end

		def to_human
			map &:to_human 
		end
=begin

Appends the arguments to the Array.

Returns the modified database-document (not the array !!)
=end
		def   append *arg
			
			@orient.update { "set #{@name.to_s} = #{@name} || #{arg.to_or} "}[@name] if check_if_complete
			@orient.reload!
		end
=begin
Append the argument to the Array, changes the Array itself.

Returns the modified Array ( and is chainable )
#
#      i= V.get( '89:0')
	#    ii=i.zwoebelkuchen <<  'z78' << 6 << [454, 787]
	#		 => [7, 5, 6, "z78", 78, 45, "z78", 6, 454, 787] 

The change is immediately transmitted to the database. 

The difference to `append`: that method accepts a komma separated list of arguments
and returns the modified database-document. `<<` accepts only one argument. An 
Array  is translated into multi-arguments of `append`

	> t =  G21.create  ll:  ['test','test_2', 5, 8 , 7988, "uzg"]
	  INFO->CREATE VERTEX ml_g21 CONTENT {"ll":["test","test_2",5,8,7988,"uzg"]}
	  => #<ML::G21:0x0000000002622cb0 @metadata={:type=>"d", :class=>"ml_g21", :version=>1, 
	  :fieldTypes=>nil, :cluster=>271, :record=>0}, 
		@attributes={:ll=>["test", "test_2", 5, 8, 7988, "uzg"]}> 
 > t.ll << [9,10]
    INFO->update #271:0 set ll = ll || [9, 10]   return after @this
     => ["test", "test_2", 5, 8, 7988, "uzg"] 
 > t.ll << [9,10] << 'u'
    INFO->update #271:0 set ll = ll || [9, 10]   return after @this
    INFO->update #271:0 set ll = ll || ['u']   return after @this
 => ["test", "test_2", 5, 8, 7988, "uzg", 9, 10]


The Array can be treated separately

  > z =  t.ll
	 => ["test", "test_2", 5, 8, 7988, "uzg"] 
  > z << 78
  INFO->update #272:0 set ll = ll || [78]   return after @this
  => ["test", "test_2", 5, 8, 7988, "uzg", 78] 

=end
		def << arg
			append( *arg).send @name 
		end

=begin

Removes the specified list entries from the Array

Returns the modified Array  (and is chainable).

 > t= G21.first
 > t.ll
   => ["test", "test_2", 7988, "uzg", 6789, "xvy"] 
 > u=  t.ll << 'xvz'
 # INFO->update #272:0 set ll = ll || ['xvz']   return after @this
 => ["test", "test_2", 7988, "uzg", 6789, "xvy", "xvz"] 
 > z=  u.remove 'xvy'
 # INFO->update #272:0 remove  ll = 'xvy'  return after @this
 => ["test", "test_2", 7988, "uzg", 6789, "xvz"] 

The ModelInstance is updated, too, as shown by calling 

> t.ll
 => ["test", "test_2", 7988, "uzg", 6789, "xvz"] 


Thus 

 > t.ll.remove 7988
 # INFO->update #272:0 remove  ll = 7988  return after @this
 => ["test", "test_2", "uzg", 6789, "xvz"] 
 
returns thea modified Array 
=end
		def remove *k
			# todo combine queries in a transaction
			ActiveOrient::Base.logger.debug { "delete: #{@name} --< #{k.map(&:to_or).join( ' :: ' )}"}
		 k.map{|l|	@orient.update( {remove: { @name => l} } ) }
	#		@orient.reload!
	#		@orient.send @name 
		end

		def remove_by_index index
			@orient.update( { remove: { @name => "#{@name[index]}" } } )
		end

def check_if_complete
	if @name.blank?
		@orient.logger.warn{ "Database is uneffected. Operation is incomplete/ not allowed" }
		false
	else
		true
	end
end
=begin
	Updating of single items
=end
		def []= key, value
			super
			@orient.update set: {@name => self} if @name.present?  if check_if_complete
		end


		###
		## just works with Hashes as parameters
		def where *item
			where_string = item.map{|m| where_string = compose_where( m ) }.join(' and ')
			subquery= OrientSupport::OrientQuery.new from: @orient, projection: "expand( #{@name})"
			q= OrientSupport::OrientQuery.new from: subquery, where: item
			@orient.db.execute{  q.to_s } if check_if_complete

		end

		def method_missing method, *args
			return if empty?
			if @orient.is_a? ActiveOrient::Model  #  IB::Model
				# delegate to public methods
				self.map{|x| x.public_send(method, *args)}
			else
				self.map{|x| x.send method, *args }
			end
		rescue NoMethodError => e
			ActiveOrient::Base.logger.error("OrientSupport::Array"){ "#{self.inspect} MethodMissing  -> Undefined method: #{args.first} --  Args: #{args[1..-1].inspect}"}
			ActiveOrient::Base.logger.error {" The Message #{e.message}"}
			ActiveOrient::Base.logger.error{ e.backtrace.map {|l| "  #{l}\n"}.join  }
			raise
		end

	end #Class




	class Hash  < Hash # WithIndifferentAccess
		include OrientSupport::Support
		def initialize modelinstance, args
			super()
			@orient = modelinstance
			self.merge! args
			@name =  block_given? ? yield : modelinstance.attributes.key(self)
			self
		end

		def store  k, v
			@orient.update { "set #{@name}[#{k.to_s.to_or}] = #{v.to_or} "}[@name] #if check_if_complete
			@orient.reload!
		end

		alias []= store


# Inserts the provided Hash to the (possibly empty) list-property and updates the dataset
		#
		# Keys are translated to symbols 
		#
		def merge **arg
			@orient.update @name => super(**arg)
			@orient.reload!
		end

		alias  << merge 

# removes a key-value entry from the hash. 
# 
# parameter: list of key's 
#
# returns the modified OrientSupport::Hash 
		#
		# ie, given
		#  b =>  <Base[51:0]: < Base: 51:0 >, a_set : {:warrant_value=>["8789", "HKD"], :what_if_pm_enabled=>["true", ""], :t_bill_value=>["0", "HKD"]}>
		# c= b.a_set.remove :warrant_value
		# INFO->update #51:0  remove a_set = 'warrant_value'   return after $current
		# c =>  {:what_if_pm_enabled=>["true", ""], :t_bill_value=>["0", "HKD"]}


		def remove *k
			# todo combine queries in a transaction

			r=  k.map{|key|	@orient.update{   "remove #{@name} = #{key.to_s.to_or} "  } }
			@orient.reload!.send @name

		end

		def delete_if &b
			super &b
			@orient.update set:{ @name => self}

		end

 # slice returns a subset of the hash 
		#
		# excepts a regular expression as well
		def slice arg
			if arg.is_a? Regexp
				find_all{ |key| key.to_s.match(arg) }.to_h
			else
				 super arg.to_sym
			end
		end
		def [] arg
			super
		end
	end
end #Module

class Hash

	  def to_human
			"{ " + self.map{ |k,v| [k.to_s,": ", v.to_orient].join }.join(', ') + " }"
		end

	#	def coerce arg
	#		if arg.is_a? DateTime
	#			nil
	#		else
	#			super
#
#			end
#		end
end