# -*- Tcl -*-
package require nx
package require nx::test

nx::Class create M {
  :method mfoo {} {puts [self proc]}
}
nx::Class create M2
nx::Class create C 
C create c1

#
# test mixin method
#
nx::test case mixin-method {
  ? {C info lookup method mixin} "::nsf::classes::nx::Class::mixin"
  ? {C mixin set M} ::M
  ? {C info precedence} "::nx::Class ::nx::Object"
  ? {C mixin get} "::M"
  ? {C info mixin classes} "::M"
  ? {c1 info precedence} "::M ::C ::nx::Object"
  ? {C mixin add M2} "::M2 ::M"
  ? {c1 info precedence} "::M2 ::M ::C ::nx::Object"
  ? {C mixin delete M2} "::M"
  ? {c1 info precedence} "::M ::C ::nx::Object"
  ? {C mixin delete M} ""
  ? {C info mixin classes} ""

  ? {C mixin set ::M} "::M"
  ? {C mixin clear} ""
  ? {C info mixin classes} ""

  ? {C mixin add ::M} "::M"
  ? {C mixin set {}} ""
  ? {C info mixin classes} ""
}

#
# test nsf::mixin interface
#
nx::test case nsf-mixin {
  ? {::nsf::mixin C ::M} "::M"
  ? {C info mixin classes} "::M"
  ? {::nsf::mixin C ::M2} "::M2 ::M"
  ? {C info mixin classes} "::M2 ::M"
  ? {::nsf::mixin C ""} ""
  ? {C info mixin classes} ""
}

#
# per-object mixins
#
nx::test case per-object-mixin {
  ? {c1 info precedence} "::C ::nx::Object"
  ? {c1 object mixin add M} ::M
  ? {::nsf::relation::get c1 object-mixin} ::M
  ? {catch {c1 object mixin UNKNOWN}} 1
  ? {::nsf::relation::get c1 object-mixin} "::M"

  # add again the same mixin
  ? {c1 object mixin add M} {::M}
  ? {c1 info precedence} "::M ::C ::nx::Object"
  ? {c1 object mixin add M2} "::M2 ::M"
  ? {c1 info precedence} "::M2 ::M ::C ::nx::Object"
  ? {c1 object mixin delete M} "::M2"
  ? {c1 info precedence} "::M2 ::C ::nx::Object"
  ? {c1 object mixin delete M2} ""
  ? {c1 info precedence} "::C ::nx::Object"

  ? {c1 object mixin add M} {::M}
  ? {c1 info object mixin classes} {::M}
  ? {c1 object mixin clear} {}
  ? {c1 info object mixin classes} {}
}

#
# adding, removing per-object mixins for classes through relation
# "object-mixin"
#
nx::test case object-mixin-relation {
  ? {::nsf::relation::set C object-mixin M} ::M
  ? {C info precedence} "::M ::nx::Class ::nx::Object"
  ? {C info object mixin classes} "::M"
  ? {::nsf::relation::set C object-mixin ""} ""
  ? {C info precedence} "::nx::Class ::nx::Object"
}

#
# adding, removing per-object mixins for classes through slot
# "object-mixin"
#
 # C object-mixin M
 # ? {C info precedence} "::M ::nx::Class ::nx::Object"
 # ? {C info object mixin classes} "::M"
 # C object-mixin ""
 # ? {C info precedence} "::nx::Class ::nx::Object"

#
# add and remove object mixin for classes via modifier "object" and
# "mixin"
#
nx::test case class+mixin {
  ? {C object mixin set M} ::M
  ? {C info precedence} "::M ::nx::Class ::nx::Object"
  ? {C info object mixin classes} "::M"
  ? {C object mixin set ""} ""
  ? {C info precedence} "::nx::Class ::nx::Object"
}

#
#  add and remove object mixin for classes via object mixin add
#
nx::test case class+mixin-add {
  ? {C object mixin add M} ::M
  ? {C info precedence} "::M ::nx::Class ::nx::Object"
  ? {C info object mixin classes} "::M"
  ? {C object mixin set ""} ""
  ? {C info precedence} "::nx::Class ::nx::Object"
  
  ? {C object mixin add M} ::M
  ? {C info precedence} "::M ::nx::Class ::nx::Object"
  ? {::nsf::relation::get C object-mixin} ::M
  ? {catch {C object mixin add UNKNOWN}} 1
  ? {::nsf::relation::get C object-mixin} "::M"
  ? {C object mixin set ""} ""
  ? {C info precedence} "::nx::Class ::nx::Object"

  ? {C object mixin set M} ::M
  ? {C info precedence} "::M ::nx::Class ::nx::Object"

  # forwarder with get
  ? {C object mixin get} "::M"
}



nx::test case mixin-add {

  nx::Class create M1 {
    :method mfoo {} {puts [current method]}
  }
  nx::Class create M11
  nx::Class create C1 

  ? {C1 info lookup method mixin} "::nsf::classes::nx::Class::mixin"
  C1 object mixin set M1
  ? {C1 info precedence} "::M1 ::nx::Class ::nx::Object"
  C1 create c11
  ? {c11 info precedence} "::C1 ::nx::Object"
  C1 object mixin add M11
  ? {C1 info precedence} "::M11 ::M1 ::nx::Class ::nx::Object"
  Object create o -object-mixin M1
  ? {o info precedence} "::M1 ::nx::Object"

  nx::Class create O 
  O object mixin set M1
  ? {O info precedence} "::M1 ::nx::Class ::nx::Object"
  nx::Class create O -object-mixin M1
  ? {O info precedence} "::M1 ::nx::Class ::nx::Object"
}

nx::test case filter-relation {
  nx::Class create CC {
    :public method filterA args {next}
    :public method filterB args {next}
    :public object method filterC args {next}
    :create cc {
      :public object method filterD args {next}
    }
  }

  ? {::nsf::relation::get cc object-filter} ""
  ? {cc info object filter methods} ""
  ? {::nsf::relation::set cc object-filter filterA} filterA
  ? {cc info object filter methods} "filterA"
  ? {cc object filter set filterB} "filterB"

  ? {::nsf::relation::get cc object-filter} "filterB"
  ? {cc info object filter methods} "filterB"

  ? {cc object filter add filterD} "filterD filterB"
  ? {::nsf::relation::get cc object-filter} "filterD filterB"
  ? {cc info object filter methods} "filterD filterB"

  ? {cc object filter delete filterB} "filterD"
  ? {::nsf::relation::get cc object-filter} "filterD"
  ? {cc info object filter methods} "filterD"

  ? {catch {::nsf::relation::set cc object-filter UNKNOWN}} 1
  ? {::nsf::relation::get cc object-filter} "filterD"
  ? {cc info object filter methods} "filterD"

  ? {::nsf::relation::get CC object-filter} ""
  ? {CC info object filter methods} ""
  ? {::nsf::relation::set CC object-filter filterC} "filterC"
  ? {::nsf::relation::get CC object-filter} "filterC"
  ? {CC info object filter methods} "filterC"

  ? {CC object filter clear} ""
  ? {::nsf::relation::get CC object-filter} ""
  ? {CC info object filter methods} ""
  
  ? {::nsf::relation::get CC class-filter} ""
  ? {CC info filter methods} ""
  ? {::nsf::relation::set CC class-filter filterA} "filterA"
  ? {::nsf::relation::get CC class-filter} "filterA"
  ? {CC info filter methods} "filterA"

  ? {CC filter add filterB} "filterB filterA"
  ? {::nsf::relation::get CC class-filter} "filterB filterA"
  ? {CC info filter methods} "filterB filterA"

  ? {CC filter delete filterA} "filterB"
  ? {::nsf::relation::get CC class-filter} "filterB"
  ? {CC info filter methods} "filterB"

  ? {catch {::nsf::relation::set CC class-filter UNKNOWN}} 1
  ? {::nsf::relation::get CC class-filter} "filterB"
  ? {CC info filter methods} "filterB"

  ? {CC filter clear} ""
  ? {::nsf::relation::get CC class-filter} ""
  ? {CC info filter methods} ""
}


nx::test configure -count 3
nx::test case "filter-and-creation" {
  nx::Class create Foo {
    :method myfilter {args} {
      set i [::incr ::count]
      set s [self]
      set m [current calledmethod]
      #puts stderr "$i: $s.$m"
      #puts stderr "$i: procsearch before [$s procsearch info]"
      set r [next]
      #puts stderr "$i: $s.$m got ($r)"
      #puts stderr "$i: $s.$m procsearch after [$s info lookup method info]"
      return $r
    }
    # method for testing next to non-existing shadowed method
    :public method baz {} {next}
  }

  ? {Foo create ob} ::ob

  # make sure, no unknown handler exists
  #? {::ob info lookup method unknown} "::nsf::classes::nx::Object::unknown"
  ? {::ob info lookup method unknown} ""

  ? {ob bar} {::ob: unable to dispatch method 'bar'}
  ? {ob baz} {}

  # define a global unknown handler
  ::nx::Object protected method unknown {m args} {
    error "[::nsf::current object]: unable to dispatch method '$m'"
  }

  ? {ob bar} {::ob: unable to dispatch method 'bar'}
  ? {ob baz} {}

  Foo filter set myfilter
  # create through filter
  ? {Foo create ob} ::ob

  # unknown through filter
  ? {ob bar1} {::ob: unable to dispatch method 'bar1'}
  ? {ob baz} {}

  # deactivate nx unknown handler in case it exists
  ::nx::Object method unknown {} {}

  # create through filter
  ? {Foo create ob2} ::ob2

  # unknown through filter
  ? {ob2 bar2} {::ob2: unable to dispatch method 'bar2'}
  ? {ob2 baz} {}

  # create with filter
  ? {Foo create ob3 -object-filter myfilter} ::ob3
}


nx::test configure -count 1
#
# Test the next-path with just intrinsic classes in cases where a
# method handle is used for method dispatch
#

nx::test case intrinsic+method-handles {
  nx::Class create A {:public method foo {} {return "A [next]"}}
  nx::Class create B -superclass A {:public method foo {} {return "B [next]"}}
  nx::Class create C -superclass B {:public method foo {} {return "C [next]"}}
  
  C create c1
  ? {c1 foo} "C B A "
  ? {c1 [C info method definitionhandle foo]} "C B A "
  ? {c1 [B info method definitionhandle foo]} "B A "
  ? {c1 [A info method definitionhandle foo]} "A "

  # we expect same results via dispatch with fully qualified names
  ? {nsf::dispatch c1 foo} "C B A "
  ? {nsf::dispatch c1 [C info method definitionhandle foo]} "C B A "
  ? {nsf::dispatch c1 [B info method definitionhandle foo]} "B A "
  ? {nsf::dispatch c1 [A info method definitionhandle foo]} "A "

  # 
  # check, whether the context of "my -local" is correct
  #
  A public method bar {} {nsf::my -local foo}
  B public method bar {} {nsf::my -local foo}
  C public method bar {} {nsf::my -local foo}

  ? {c1 bar} "C B A "
  ? {c1 [C info method definitionhandle bar]} "C B A "
  ? {c1 [B info method definitionhandle bar]} "B A "
  ? {c1 [A info method definitionhandle bar]} "A "
}

#
# Test the next-path with mixin classes in cases where a
# method handle is used for method dispatch
#

nx::test case mixins+method-handles {
  #
  # Just mixin classes
  #
  nx::Class create A {:public method foo {} {return "A [next]"}}
  nx::Class create B {:public method foo {} {return "B [next]"}}
  nx::Class create C {:public method foo {} {return "C [next]"}}

  nx::Class create X -mixin {C B A}
  X create c1
  ? {c1 foo} "C B A "
  ? {c1 [C info method definitionhandle foo]} "C B A "
  ? {c1 [B info method definitionhandle foo]} "B A "
  ? {c1 [A info method definitionhandle foo]} "A "
  
  #
  # Intrinsic classes and mixin classes
  #

  nx::Class create Y {:public method foo {} {return "Y [next]"}}
  nx::Class create Z -superclass Y {:public method foo {} {return "Z [next]"}}

  Z create c1 -object-mixin {C B A}
  ? {c1 foo} "C B A Z Y "
  ? {c1 [C info method definitionhandle foo]} "C B A Z Y "
  ? {c1 [B info method definitionhandle foo]} "B A Z Y "
  ? {c1 [A info method definitionhandle foo]} "A Z Y "
  ? {c1 [Z info method definitionhandle foo]} "Z Y "
  ? {c1 [Y info method definitionhandle foo]} "Y "

  # 
  # check, whether the context of "my -local" is correct
  #
  A public method bar {} {nsf::my -local foo}
  B public method bar {} {nsf::my -local foo}
  C public method bar {} {nsf::my -local foo}
  Y public method bar {} {nsf::my -local foo}
  Z public method bar {} {nsf::my -local foo}

  ? {c1 bar} "C B A Z Y "
  ? {c1 [C info method definitionhandle bar]} "C B A Z Y "
  ? {c1 [B info method definitionhandle bar]} "B A Z Y "
  ? {c1 [A info method definitionhandle bar]} "A Z Y "
  ? {c1 [Z info method definitionhandle bar]} "Z Y "
  ? {c1 [Y info method definitionhandle bar]} "Y "

}


#
# Test the next-path with mixin classes in cases where a
# method handle is used for method dispatch
#

nx::test case mixins+method-handles+intrinsic {
  #
  # Just mixin classes
  #
  nx::Class create A {:public method foo {} {return "A [next]"}}
  nx::Class create B {:public method foo {} {return "B [next]"}}
  nx::Class create C {:public method foo {} {return "C [next]"}}

  nx::Class create X -mixin {C B A} {
    :public method foo {} {return "X [next]"}
  }
  X create c1
  ? {c1 foo} "C B A X "
  ? {nsf::dispatch c1 -intrinsic foo} "X "

  
  #
  # Intrinsic classes and mixin classes
  #

  nx::Class create Y {:public method foo {} {return "Y [next]"}}
  nx::Class create Z -superclass Y {:public method foo {} {return "Z [next]"}}

  Z create c1 -object-mixin {C B A}
  ? {c1 foo} "C B A Z Y "
  ? {nsf::dispatch c1 -intrinsic foo} "Z Y "

  # 
  # check, whether the context of "my -intrinsic" is correct
  #
  A public method bar {} {nsf::my -intrinsic foo}
  B public method bar {} {nsf::my -intrinsic foo}
  C public method bar {} {nsf::my -intrinsic foo}
  Y public method bar {} {nsf::my -intrinsic foo}
  Z public method bar {} {nsf::my -intrinsic foo}

  ? {c1 info precedence} "::C ::B ::A ::Z ::Y ::nx::Object"
  ? {c1 bar} "Z Y "
  ? {c1 [C info method definitionhandle bar]} "Z Y "
  ? {c1 [B info method definitionhandle bar]} "Z Y "
  ? {c1 [A info method definitionhandle bar]} "Z Y "
  ? {c1 [Z info method definitionhandle bar]} "Z Y "
  ? {c1 [Y info method definitionhandle bar]} "Z Y "

  # 
  # check, whether the context of "nsf::dispatch [self] -intrinsic" is correct
  #
  A public method bar {} {nsf::dispatch [self] -intrinsic foo}
  B public method bar {} {nsf::dispatch [self] -intrinsic foo}
  C public method bar {} {nsf::dispatch [self] -intrinsic foo}
  Y public method bar {} {nsf::dispatch [self] -intrinsic foo}
  Z public method bar {} {nsf::dispatch [self] -intrinsic foo}

  ? {c1 bar} "Z Y "
  ? {c1 [C info method definitionhandle bar]} "Z Y "
  ? {c1 [B info method definitionhandle bar]} "Z Y "
  ? {c1 [A info method definitionhandle bar]} "Z Y "
  ? {c1 [Z info method definitionhandle bar]} "Z Y "
  ? {c1 [Y info method definitionhandle bar]} "Z Y "
}

#
# Test filter guards (define filter and guard separtely)
#

nx::test case filter-guard-separately {

  #
  # Define a room with occupancy and methods for entering and leaving
  #
  nx::Class create Room {
    :property name
    :variable occupancy:integer 0

    :public method enter {name} {incr ::occupancy}
    :public method leave {name} {incr ::occupancy -1}
    
    #
    # We are interested, what happens with the room, so we define a
    # logging filter....
    #
    :method loggingFilter args {
      lappend ::_ [current calledmethod]
      next
    }
    
    #
    # ... and we register it.
    #
    :filter add loggingFilter
  }

  set ::_ {}

  ? {Room create r} ::r
  r enter Uwe
  r leave Uwe
  r configure -name "Office"
  ? {set ::_} "__objectparameter init enter leave configure"

  #
  # Hmm, we not so much interested on all these calls. Just the
  # "enter" and "leave" operations are fine. We could have certainly
  # as well mixin for these two methods, but the guards are more
  # general since the can as well trigger on arbitrary patterns.
  #

  Room filter guard loggingFilter {
    [current calledmethod] in {enter leave}
  }

  r destroy
  set ::_ {}

  ? {Room create r} ::r
  r enter Uwe
  r leave Uwe
  r configure -name "Office"
  ? {set ::_} "enter leave"

  r destroy
  
  # Now we define a subclass DangerRoom, which refines the filter by
  # logging into a "dangerRoomLog". We want here entries for all
  # operations.

  set ::_ {}
  set ::dangerRoomLog {}

  nx::Class create DangerRoom -superclass Room {
    :method loggingFilter args {
      lappend ::dangerRoomLog [current calledmethod]
      next
    }
    :filter add loggingFilter
  }

  ? {DangerRoom create d} ::d
  d enter Uwe
  d leave Uwe
  d configure -name "Safe Room"
  ? {set ::_} "enter leave"
  ? {expr [llength $::dangerRoomLog] > 2} 1

  d destroy

}

#
# Test filter guards (define filter together with guard)
#

nx::test case filter-guard-separately {

  #
  # Define a room with occupancy and methods for entering and leaving
  #
  nx::Class create Room {
    :property name
    :variable occupancy:integer 0

    :public method enter {name} {incr ::occupancy}
    :public method leave {name} {incr ::occupancy -1}
    
    #
    # We are interested, what happens with the room, so we define a
    # logging filter....
    #
    :method loggingFilter args {
      lappend ::_ [current calledmethod]
      next
    }
    
    #
    # ... and we register it together with a guard.
    #
    :filter add {loggingFilter {
      [current calledmethod] in {enter leave}
    }}
  }

  set ::_ {}

  ? {Room create r} ::r
  r enter Uwe
  r leave Uwe
  r configure -name "Office"
  ? {set ::_} "enter leave"
  
  # Now we define a subclass DangerRoom, which refines the filter by
  # logging into a "dangerRoomLog". We want here entries for all
  # operations.

  set ::_ {}
  set ::dangerRoomLog {}

  nx::Class create DangerRoom -superclass Room {

    :method loggingFilter args {
      lappend ::dangerRoomLog [current calledmethod]
      next
    }
    :filter add loggingFilter
  }

  ? {DangerRoom create d} ::d
  d enter Uwe
  d leave Uwe
  d configure -name "Safe Room"
  ? {set ::_} "enter leave"
  ? {expr [llength $::dangerRoomLog] > 2} 1

  d destroy
}


#
# Local variables:
#    mode: tcl
#    tcl-indent-level: 2
#    indent-tabs-mode: nil
# End: