# $Id: slottest.xotcl,v 1.5 2007/08/14 16:38:27 neumann Exp $
package require XOTcl 1
namespace import -force xotcl::*
package require xotcl::test 1

proc ? {cmd expected} {
  set t [Test new -cmd $cmd -count 100]
  $t expected $expected 
  $t run
}

proc t {cmd expected {txt ""}} {
  set t [Test new -cmd $cmd -msg $txt]
  $t expected $expected 
  $t run
}

# what's new:
#   - slots instances are manager objects for slot values
#   - generalization of slots to have different kind of domains and managers
#   - slots for objects and classes (slot parameter 'per-object' true|false, 
#     when to used on a class object)
#   - works for mixins/filters/class/superclass (e.g ... superclass add ::M)
#   - initcmd and valuecmd 
#     initcmd: is executed when the instance variable is read the first time
#     valuecmd: is executed whenever the instance variable is read
#     (implemented via trace; alternate approach for similar behavior
#     is to define per-object procs for get/assign, see e.g. slots for
#     class and superclass; slots require methods to be invoked, 
#     not var references;
#     otoh, trace are somewhat more fragile and harder to debug)
#     default, initcmd and valuecmd are to be used mutually exclusively
#   - valuechangedcmd: executed after the change of an instance variable,
#     can be used e.g. for validation
#
# -gustaf neumann                          21.Jan. 2006

package require xotcl::serializer 1

#proc ? {cmd expected} {
#   set r [eval $cmd]		       
#   if {$r ne $expected} {error "$cmd returned '$r' ne '$expected'"}
#}

# proc t {cmd {txt ""}} {
#   set n 1000
#   #set ms [lindex [time [list time $cmd $n] 10] 0]
#   set ms [lindex [time [list time $cmd $n] 5] 0]
#   puts "[format %7.4f [expr {$ms*1.0/$n}]]ms for [format %-30s $cmd] ($txt)"
# }
#######################################################
# testing __initcmds
set ::hu 0
proc T1 {var sub op} {c1 set $var t1}
proc T2 {var sub op} {c1 set $var t2}
Class C -array set __initcmds {
  x {set x 1} 
  y {incr ::hu} 
  z {my trace add variable z read T1}}
C create c1
? {c1 info vars x} x
? {c1 info vars y} ""
? {c1 set x} 1
? {set ::hu} 1

Class D -array set __initcmds {
  x {set x 2} 
  z {my trace add variable z read T2}} -superclass C
D create c1
? {c1 set x} 2
? {c1 set z} t2
? {set ::hu} 2

#######################################################
# 
# a small helper
Object instproc slots cmds {
  if {![my isobject [self]::slot]} {Object create [self]::slot}
  namespace eval [self]::slot $cmds
}

######################
# system slots
######################

Class M
Class O -mixin M
? {catch {Object o -mixin check1 M}} 1
? {O mixin} ::M
Class M2
O mixin add M2
? {O mixin} {::M2 ::M}
O mixin M2
? {O mixin} ::M2
O mixin ""
? {O mixin} ""
O mixin set M ;# not sure, whether we should keep set here, or use assign or some better term
? {O mixin} ::M
? {O mixin ""} ""

# with slots like class etc. we have to option to 
# a) rename the original command like in the following
# b) provide a no-op value, such that we define only meta-data in the slot
# c) define a low-level tcl command like setrelation (or extend it) to handle the setter

# "class" is not multivalued, therefore we should not add (or remove) add/delete 
# from the set of subcommands...
? {::xotcl::InfoSlot class} "::xotcl::MetaSlot"
O o1
? {o1 class} "::O"
o1 class Object
? {o1 class} "::xotcl::Object"
? {catch {o1 class add M}} 1


? {O superclass} "::xotcl::Object"
Class O2 -superclass O

#t {O2 superclass O} "superclass 1"
? {O superclass} "::xotcl::Object"

::xotcl::Slot instproc slot {object name property} {
  switch $property {
    self {return [self]}
    domain {return [my domain]}
  }
}

? {O superclass slot self} "::xotcl::Class::slot::superclass"
? {O superclass slot domain} "::xotcl::Class"


? {O2 superclass} "::O"
O2 superclass add M
? {O2 superclass} "::M ::O"
O2 superclass delete ::O
? {O2 superclass} "::M"


# the main difference between an Attribute and a Role is that 
# it references some other objects
#Class Attribute -superclass ::xotcl::Slot
Class Role -superclass Attribute -parameter {references}

######################
# application classes
######################
Class Person -slots {
  Attribute name
  Attribute age -default 0
}

Class Article -slots {
  Attribute title
  Attribute date
}

Class publishes -slots {
  Role written_by -references Person -multivalued true
  Role has_published -references Paper -multivalued true
}

Class Project -slots {
  Attribute name
  Role manager -references Person
  Role member -references Person -multivalued true
}

puts [Person serialize]
Person slot name default "gustaf"
? {Person slot name default} gustaf
Person p1 -name neophytos
? {p1 name} neophytos
? {p1 age} 0
p1 age 123
? {p1 age} 123


Object o1
o1 set i 0
::xotcl::alias o1 Incr -objscope ::incr
t {o1 incr i} 1 "method incr"
t {o1 Incr i} 1002 "aliased tcl incr"
t {o1 incr i} 2003 "method incr"
t {o1 Incr i} 3004 "aliased tcl incr"

::xotcl::alias ::xotcl::Object Set -objscope ::set
t {o1 set i 1} 1 "method set"
t {o1 set i} 1 "method set"
t {o1 Set i 1} 1 "aliased tcl set"
t {o1 Set i} 1 "aliased tcl set"
::xotcl::alias o1 Set -objscope ::set
t {o1 Set i 1} 1 "aliased object tcl set"
t {o1 Set i} 1 "aliased object tcl set"
::xotcl::Object instforward SSet -earlybinding -objscope ::set
t {o1 SSet i 1} 1 "forward earlybinding tcl set"
t {o1 SSet i} 1 "forward earlybinding tcl set"
#exit

o1 set z 100
#o1 forward z o1 [list %argclindex [list set set]] %proc
#o1 proc get name {my set $name}
o1 forward get -earlybinding ::xotcl::setinstvar %self %1
? {o1 get z 101} 101
? {o1 get z} "101"

t {o1 get z} 101 "get value via new parametercmd get"
t {o1 get z 124} 124 "set value via new parametercmd get"


o1 forward zz -earlybinding ::xotcl::setinstvar %self %proc
? {o1 zz 123} 123
? {o1 zz} 123

t {o1 zz} 123 "parametercmd forward earlybinding setinstvar"
t {o1 zz 124} 124 "parametercmd forward earlybinding setinstvar"

o1 forward z2 -earlybinding -objscope ::set %proc
t {o1 z2 111} 111 "parametercmd forward earlybinding tcl set"
t {o1 z2} 111 "parametercmd forward earlybinding tcl set"

o1 forward z3 -objscope ::set %proc
t {o1 z3 111} 111 "parametercmd forward tcl set"
t {o1 z3} 111 "parametercmd forward tcl set"

o1 set y 11
o1 parametercmd y
t {o1 y} 11 "parametercmd"
t {o1 y 1} 1 "parametercmd"

#Class C -parameter {a {b 10} {c "Hello World"}}
#C copy V

#puts [C serialize]
#puts [V serialize]

#C destroy
#V v1
#puts [v1 b]

# ::xotcl::Object instproc param arglist {
#   foreach arg $arglist {
#     puts "arg=$arg"
#     set l [llength $arg]
#     set name [lindex $arg 0]
#     if {![my isobject [self]::slot]} {::xotcl::Object create [self]::slot}
#     if {$l == 1} {
#       Attribute create [self]::slot::$name
#     } elseif {$l == 2} {
#       Attribute create [self]::slot::$name  -default [lindex $arg 1]
#     } else {
#       set paramstring [string range $arg [expr {[string length $name]+1}] end]
#       #puts  stderr "remaining arg = '$paramstring'"
#       if {[string match {[$\[]*} $paramstring]} {
# 	#puts stderr "match,     $cl set __defaults($name) $paramstring"
# 	Attribute create [self]::slot::$name -default $paramstring
# 	continue
#       }
#     }
#   }
# }


::xotcl::Slot instmixin delete ::xotcl::Slot::Optimizer

Class C1 -parameter {a {b 10} {c "Hello World"}}
C1 c1 -a 1
? {c1 a} 1
? {c1 b} 10
? {c1 c} "Hello World"

##### is short form of 


Class C2 -slots {
  Attribute a 
  Attribute b -default 10
  Attribute c -default "Hello World"
}
C2 c2 -a 1
? {c2 procsearch a} "::C2 instforward a"
? {c2 a} 1
? {c2 b} 10
? {c2 c} "Hello World"


t {c2 a} 1 "new indirect parametercmd"
t {c2 a 1} 1 "new indirect parametercmd"

::xotcl::Slot instmixin add ::xotcl::Slot::Optimizer
Class C3 -slots {
  Attribute a 
  Attribute b -default 10
  Attribute c -default "Hello World"
}
C3 c3 -a 1
? {c3 procsearch a} "::C3 instparametercmd a"
? {c3 a} 1
? {c3 b} 10
? {c3 c} "Hello World"

t {c3 a} 1 "new indirect parametercmd optimized"
t {c3 a 1} 1 "new indirect parametercmd optimized"

####### nasty names
Class create D -slots {
  Attribute create create -default 1
}
D d1

####### gargash 2
Class create A -parameter {{foo 1}}
# or
Class create A -slots {
  Attribute foo -default 1
}

A create a1 -foo 234 ;# calls default foo setter

A instproc f1 {} {puts hu}
A instforward f2 puts hu
A create a0
#a0 f1
a0 proc f3 {} {puts hu}
a0 forward f4 puts hu
? {a0 procsearch f1} "::A instproc f1"
? {a0 procsearch f2} "::A instforward f2"
? {a0 procsearch f3} "::a0 proc f3"
? {a0 procsearch f4} "::a0 forward f4"
? {a0 procsearch set} "::xotcl::Object instcmd set"
? {A slot foo procsearch assign} "::xotcl::Slot instcmd assign"

# redefine setter for foo of class A
A slot foo proc assign {domain var val} {
  # Do something with [self] that isn't valid pre-init
  puts setter-[self proc]
  $domain set $var $val
}

a1 foo ;# calls default foo getter
a1 foo 123 ;# calls overridden foosetter
? {a1 foo} 123


#puts [A serialize]

###################
# Application Slots
#

Class Person -slots {
  Attribute name
  Attribute age -default 0
  Attribute projects -default {} -multivalued true
}

Person p1 -name "Gustaf"
? {p1 name} Gustaf
? {p1 age} 0
? {p1 projects} {}

Class Project -slots {
  Attribute name 
  Attribute description
  Attribute manager
  Attribute member -multivalued 1
}
  
Project project1 -name XOTcl -description "A highly flexible OO scripting language"

p1 projects add ::project1
? {p1 projects} ::project1
#p1 projects add some-other-value
#? {p1 projects} "some-other-value ::project1"

::xotcl::Slot instproc check {
  {-keep_old_value:boolean true} 
  value predicate type obj var
} {
  puts "+++ checking $value with $predicate ==> [expr $predicate]"
  if {![expr $predicate]} {
    if {[$obj exists __oldvalue($var)]} {
      $obj set $var [$obj set __oldvalue($var)]
    } else {
      $obj unset $var
    }
    error "$value is not of type $type"
  }
  if {$keep_old_value} {$obj set __oldvalue($var) $value}
}

::xotcl::Slot instproc checkall {values predicate type obj var} {
  foreach value $values {
    my check -keep_old_value false $value $predicate $type $obj $var
  }
  $obj set __oldvalue($var) $value
}
    
Person slots {
  Attribute projects -default "" -multivalued true -type ::Project
  Attribute salary -type integer
}

Person p2 -name "Gustaf"
p2 projects add ::project1
? {catch {p2 projects add ::o1}} 1
p2 salary 100
? {catch {p2 salary 100.9}} 1
? {p2 salary} 100
p2 append salary 9
? {p2 salary} 1009
? {catch {p2 append salary b}} 1
? {p2 salary} 1009

Person slots {
  Attribute sex -type "my sex" -proc sex {value} {
    switch -glob $value {
      m* {my uplevel {$obj set $var m}; return 1}
      f* {my uplevel {$obj set $var f}; return 1}
      default {return 0}
    }
  }
}
Person p3 -sex male
? {p3 sex} m

set ::hu 0
Class C -slots {
  Attribute x -initcmd {incr ::hu; set x 101}
}
C c1
? {c1 info vars} ""
? {c1 set x} 101
? {c1 info vars} "x"
? {set ::hu 1} 1


Class Point -parameter {{x 100} {y 300}}
Class Rectangle -parameter {color}

Rectangle r0 -color pink -contains {
  Rectangle r1 -color red -contains {
    Point x1 -x 1 -y 2
    Point x2 -x 1 -y 2
  }
  Rectangle r2 -color green -contains {
    Point x1
    Point x2
  }
}

? {r0 color} pink
? {r0 r1 color} red
? {r0 r1 x1 x} 1
? {r0 r1 x2 y} 2
? {r0 r2 color} green
#puts [r0 serialize]


#####
Class create A -slots {
  Attribute foo -default 1 -proc assign { domain var value} {
    if {$value < 0 || $value > 99} {
      error "$value is not in the range of 0 .. 99"
    }
    $domain set $var $value
  }
}

A create a1
? {a1 foo 10} 10
? {a1 foo 20} 20
? {a1 foo} 20
? {catch {a1 foo -1}} 1
? {catch {a1 foo 100}} 1
? {catch {a1 foo 99}} 0

set x [Object new -set x 1 -contains {
  Object new -set x 1.1
  Object new -set x 1.2 -contains {
    Object new -set x 1.2.1
    Object new -set x 1.2.2 -contains {
      Object new -set x 1.2.2.1
    }
    Object new -set x 1.2.3
  }
  Object new -set x 1.3
}]

? {llength [$x info children]} 3
? {llength [[lindex [lsort [$x info children]] 0] info children]} 0
? {llength [[lindex [lsort [$x info children]] 1] info children]} 3
? {llength [[lindex [lsort [$x info children]] 2] info children]} 0

# / / / / / / / / / / / / / / / / / / / / / / /
# Tests specific to per-object attribute slots
#

Class create C -slots {
  Attribute foo \
      -per-object true \
      -default 1
}

C create c

? {C procsearch foo} "::C parametercmd foo"
? {c procsearch foo} ""
? {C slot foo procsearch assign} "::xotcl::Slot instcmd assign"
? {C exists foo} 1
? {C set foo} 1
t {C foo} 1 "retrieves default value"
C foo 123
t {C foo} 123 "custom defined value"

C destroy

#
# basics (without default)
#

Class create C -slots {
  Attribute bar \
      -per-object true
}

C create c

? {C procsearch bar} "::C parametercmd bar"
? {c procsearch bar} ""
? {C slot bar procsearch assign} "::xotcl::Slot instcmd assign"
? {C exists bar} 0
? {C bar 1} 1
? {C bar} 1
? {c exists bar} 0

C destroy; c destroy

#
# basics (with default)
#

Class create C -slots {
  Attribute foo \
      -per-object true \
      -default 1
}

C create c

? {C procsearch foo} "::C parametercmd foo"
? {c procsearch foo} ""
? {C slot foo procsearch assign} "::xotcl::Slot instcmd assign"
? {C exists foo} 1
? {C set foo} 1
t {C foo} 1 "retrieves default value"
C foo 123
t {C foo} 123 "custom defined value"

# default is to be skipped when variable is already
# initialised

C set bar x
C slots {
  Attribute bar \
      -per-object true \
      -default y
}

? {C exists bar} 1
? {C bar} x

C destroy; c destroy

#
# trace-based hooks for per-object attributes
#

# a. initcmd
set ::__verify 0
Class C -slots {
  Attribute bar \
      -per-object true \
      -initcmd {incr ::__verify; set _ 101}
}

C create c

? {C exists bar} 1
? {set ::__verify} 1
? {C bar} 101
? {C exists bar} 1
? {set ::__verify} 1

# b. valuecmd

C slots {
  Attribute bar2 \
      -per-object true \
      -valuecmd {
	set ::__verify 2; set _ 101
      }
}

? {C exists bar2} 1
? {set ::__verify} 2
? {C bar2} 101
? {set ::__verify} 2
? {C bar2 202} 202

# c. valuechangedcmd
C slots {
  Attribute bar3 \
      -per-object true \
      -valuechangedcmd {
	set ::__verify 3; $obj set $var 909
      }
}

? {C exists bar3} 0
? {set ::__verify} 2
? {C bar3 101} 909
? {set ::__verify} 3
? {C bar3 102} 909

C destroy; c destroy

#exit

#puts [Person array get __defaults]
#puts [Person serialize]
#puts [Serializer all]
#eval [Serializer all]

? {p2 salary} 1009
? {catch {p2 append salary b}} 1
? {p2 salary} 1009
#p2 projects add ::o1

p1 set x 0
t {p1 set x} 0 "get instvar value via set"
t {p1 set x 1} 1 "set instvar value via set"


Object o1
proc f {x} {return $x}
o1 forward myf -earlybinding f
? {o1 myf abc} abc

rename f ""
proc f {x} {return 11}
? {o1 myf abc} 11

Object o2
o2 proc f {x} {expr {$x*2}}
o1 forward myf -earlybinding o2 f

? {o1 myf 100} 200

o1 set x 42
o1 forward x -earlybinding ::xotcl::setinstvar %self %proc
? [list o1 x] 42
? [list o1 x 41] 41
t {o1 x} 41 "get parametercmd via forward (earlybinding)"
t {o1 x 41} 41 "set parametercmd via forward (earlybinding)"

#obj forward Mixin -default {getter setter} mixin %1  %self
o1 forward z -default {getter setter} %self

o1 forward myfset -objscope set
o1 myfset y 102
? {o1 myfset y} 102

t {o1 myfset y} 102 "get instvar value via forward"
t {o1 myfset y 122} 122 "set instvar value via forward"

o1 forward myfdset -earlybinding -objscope set
o1 myfdset y 103
? {o1 myfdset y} 103

t {o1 myfdset y} 103 "get instvar value via forward -earlybinding"
t {o1 myfdset y 123} 123 "set instvar value via forward -earlybinding"

::xotcl::alias o1 myset -objscope ::set
o1 myset x 101
? {o1 myset x} 101

t {o1 myset x} 101 "get instvar value via set alias"
t {o1 myset x 123} 123 "set instvar value via set alias"

Class Fred -slots { Attribute create a -initcmd { set _ 4 } }
? {Fred x} ::x
? {x a 4} 4
x move y
? {y a} 4

exit

# t {p1 age} 0 "slot read"
# Class P -parameter {age {s -setter sets}}
# P instproc sets {var value} {
#   my set $var $value
# }
# P create p2 -age 345 -s 567

# t {p2 age} "parametercmd read"
# t {::xotcl::setinstvar p2 age} "via setinstvar"
# t {p2 s} "parameter read with setter"


::xotcl::Attribute create Project::slot::fullbudget \
    -initcmd {puts stderr init-on-[self];my set __x 100} \
    -valuechangedcmd {
      puts "budget is now [my set fullbudget]" 
      my set __x [my set fullbudget] 
    }

::xotcl::Attribute create Project::slot::currentbudget -valuecmd {puts stderr change-on-[self];my incr __x -1}

Person p2 -name gustaf
Person p3 -name frido
Article a1 -title "My life as a saint" -date "1.1.2006"
publishes new -written_by p1 -has_published a1
set p [Project new -name icamp -manager p2 -member add p1 -member add p3]
$p member add X end
puts [$p member]

? [list $p fullbudget] 100
? [list $p fullbudget] 100
? [list $p currentbudget] 99
? [list $p currentbudget] 98
? [list $p fullbudget 200] 200
? [list $p currentbudget] 199
exit

foreach o [Object allinstances] {
  if {[$o isclass]} continue
  if {[$o istype Slot]} continue
  if {$o eq "::xotcl::relmgr"} continue
  set string "$o\n"
  foreach v [$o info vars] {
    append string "   $v = [$o set $v]\n"
  }
  puts $string
}

puts stderr DONE-[p1 name]-[p1 age]

p3 age 77
exit

puts [XoXML asXML]

puts ====
foreach n [XoXML selectNodes {//object[age > 1]}]  {puts [$n asXML]}

exit
