# UserLand Validator methods
#  The TclSOAP package provides an implementation of the UserLand 
#  SOAP interoperability methods. This procedure provides the client side
#  for these methods. Either see validator.soapware.org or the notes in each
#  of the implementation files in the TclSOAP/cgi-bin/soap directory.
#

package require SOAP
package require XMLRPC
package require SOAP::http
package require base64

package require rpcvar
namespace import -force rpcvar::*

set help {
    Call 'validator_soap_clients url' or 'validator_xmlrpc_clients url' to
    define the methods for a given endpoint URL.
    
    Call 'validate_soap url' or 'validate_xmlrpc url' to run the test suite
    against an endpoint.
}

# The types for the nested type test...
typedef {
    larry int
    curly int
    moe int
} Stooges

typedef {
    day01 Stooges
    day02 Stooges
    day03 Stooges
} month

typedef {
    month03 month
    month04 month
} year

typedef {
    year2000 year
} myStruct

typedef {
    substruct0 Stooges
    substruct1 Stooges
    substruct2 Stooges
    substruct3 Stooges
    substruct4 Stooges
} StoogeSet

# The XMLRPC nestedStructTest method uses different member names. Bah.
typedef {01 Stooges 02 Stooges 03 Stooges} XMonth
typedef {03 Xmonth 04 Xmonth} XYear
typedef {2000 XYear} XMyStruct

proc validator_soap_clients {{proxy http://localhost/cgi-bin/rpc}} {
    SOAP::create countTheEntities \
	    -proxy $proxy -params {s string}
    SOAP::create easyStructTest \
	    -proxy $proxy -params {stooges struct}
    SOAP::create echoStructTest \
	    -proxy $proxy -params {myStruct StoogeSet}
    SOAP::create manyTypesTest \
	    -proxy $proxy \
            -params {num int bool boolean state int doub double \
	             dat timeInstant bin string}
    SOAP::create moderateSizeArrayCheck \
	    -proxy $proxy -params {myArray array}
    SOAP::create nestedStructTest \
	    -proxy $proxy -params {myStruct myStruct}
    SOAP::create simpleStructReturnTest \
	    -proxy $proxy -params {myNumber int}
    SOAP::create whichToolkit \
	    -uri http://www.soapware.org/ \
	    -proxy $proxy -params {}
}

proc validator_xmlrpc_clients {{proxy http://localhost/cgi-bin/rpc}} {
    XMLRPC::create countTheEntities \
	    -name validator1.countTheEntities \
	    -proxy $proxy -params {s string}
    XMLRPC::create easyStructTest \
	    -name validator1.easyStructTest \
	    -proxy $proxy -params {stooges struct}
    XMLRPC::create echoStructTest \
	    -name validator1.echoStructTest \
	    -proxy $proxy -params {myStruct StoogeSet}
    XMLRPC::create manyTypesTest \
	    -name validator1.manyTypesTest \
	    -proxy $proxy \
            -params {num int bool boolean state int doub double \
	             dat string bin string}
    XMLRPC::create moderateSizeArrayCheck \
	    -name validator1.moderateSizeArrayCheck\
	    -proxy $proxy -params {myArray array}
    XMLRPC::create arrayOfStructsTest \
	    -name validator1.arrayOfStructsTest \
	    -proxy $proxy -params {myArray Stooges()}
    XMLRPC::create nestedStructTest \
	    -name validator1.nestedStructTest \
	    -proxy $proxy -params {myStruct XMyStruct}
    XMLRPC::create simpleStructReturnTest \
	    -name validator1.simpleStructReturnTest \
	    -proxy $proxy -params {myNumber int}
    XMLRPC::create whichToolkit \
	    -name validator1.whichToolkit \
	    -proxy $proxy -params {}
}

# -------------------------------------------------------------------------

proc validate_soap {{proxy http://localhost/cgi-bin/rpc}} {
    validator_soap_clients $proxy
    validate_protocol
    catch {validate.nestedStructTest} msg       ; puts "$msg"
}

proc validate_xmlrpc {{proxy http://localhost/cgi-bin/rpc}} {
    validator_xmlrpc_clients $proxy
    validate_protocol
    catch {validate.arrayOfStructsTest} msg     ; puts "$msg"
    catch {validate.xnestedStructTest} msg      ; puts "$msg"
}

proc validate_protocol {} {
    catch {validate.countTheEntities} msg       ; puts "$msg"
    catch {validate.easyStructTest} msg         ; puts "$msg"
    catch {validate.moderateSizeArrayCheck} msg ; puts "$msg"
    catch {validate.simpleStructReturnTest} msg ; puts "$msg"
    catch {validate.echoStructTest} msg         ; puts "$msg"
    catch {validate.manyTypesTest} msg          ; puts "$msg"
}

# -------------------------------------------------------------------------
# Helper methods used in generating data for the requests.

# Get a random integer between -100 and +100
proc randVal {} {
    return [expr int(rand() * 200 - 100)]
}

# Create a struct list with three named elements and three random int values.
proc stoogeStruct {} {
    return [list larry [randVal] curly [randVal] moe [randVal]]
}

# -------------------------------------------------------------------------

proc validate.countTheEntities {} {
    array set ents [countTheEntities {<""><&><'><}]
    
    if {$ents(ctQuotes) != 2} {
	error "ctQuotes is incorrect" 
    }
    if {$ents(ctLeftAngleBrackets) != 4} {
	error "ctLeftAngleBrackets is incorrent" 
    }
    if {$ents(ctAmpersands) != 1} {
	error "ctAmpersands is incorrect" 
    }
    if {$ents(ctApostrophes) != 1} {
	error "ctApostrophes is incorrent"
    }
    if {$ents(ctRightAngleBrackets) != 3} {
	error "ctRightAngleBrackets is incorrent" 
    }
    return "countTheEntities"
}

proc validate.easyStructTest {} {
    array set stooges [stoogeStruct]
    set check [expr $stooges(larry) + $stooges(curly) + $stooges(moe)]

    set r [easyStructTest [array get stooges]]
    if {$r != $check} { 
	error "easyStructTest failed: $r != $check" 
    }
    return "easyStructTest"
}

proc validate.echoStructTest {} {
    set q [list \
	    substruct0 [stoogeStruct] \
	    substruct1 [stoogeStruct] \
	    substruct2 [stoogeStruct] \
	    substruct3 [stoogeStruct] \
	    substruct4 [stoogeStruct] ]
    set r [echoStructTest $q]
    if {[llength $q] != [llength $r]} {
	error "echoStructTest failed: list lengths differ"
    }
    
    array set aq $q
    array set ar $r

    foreach substruct [array names aq] {
        if {[llength $ar($substruct)] != [llength $aq($substruct)]} {
            error "echoStructTest failed: $substruct lengths differ"
        }
        array set asq $aq($substruct)
        array set asr $ar($substruct)
        foreach stooge [array names asq] {
            if {$asq($stooge) != $asr($stooge)} {
                error "echoStructTest failed: $substruct.$stooge\
                    $asq($stooge) != $asr($stooge)"
            }
	}
    }
    return "echoStructTest"
}

proc validate.manyTypesTest {} {
    set i [randVal]
    set b 1 ;#true
    set st [expr int(rand() * 48) + 1]
    set dbl [expr rand() * 200]
    set secs [clock seconds]
    set date [clock format $secs -format {%Y-%m-%dT%H:%M:%S}]
    set bin [base64::encode [string repeat "HelloWorld!" 24]]
    set r [manyTypesTest $i $b $st $dbl $date $bin]
    
    if {[lindex $r 0] != $i} {error "manyTypesTest failed int"}
    if {[lindex $r 1] && ! $b} {error "failed bool: [lindex $r 1] != $b"}
    if {[lindex $r 2] != $st} {error "manyTypesTest failed state"}
    if {! [expr [lindex $r 3] == $dbl]} {error "manyTypesTest failed double"}
    set rsecs [clock scan [string range \
                               [string map {- {}} [lindex $r 4]] \
                               0 16]]
    if {$rsecs != $secs} {
        error "manyTypesTest failed date: [lindex $r 4] != $date"
    }
    if {[lindex $r 5] != $bin} {error "manyTypesTest failed bin"}
    return "manyTypesTest"
}

proc validate.moderateSizeArrayCheck {} {
    for {set n 12} {$n != 0} {incr n -1} {
	lappend q [randVal]
    }
    set check "[lindex $q 0][lindex $q end]"
    set r [moderateSizeArrayCheck $q]
    if {! [string match $check $r]} {
	error "moderateSizeArrayCheck failed: $check != $r"}
    return "moderateSizeArrayCheck"
}

proc validate.simpleStructReturnTest {} {
    set q [randVal]
    array set r [simpleStructReturnTest $q]
    if {$r(times10) != [expr $q * 10]} {
	error "simpleStructReturnTest: $q * 10 is not $r(times10)"
    }
    if {$r(times100) != [expr $q * 100]} {
	error "simpleStructReturnTest: $q * 100 is not $r(times100)"
    }
    if {$r(times1000) != [expr $q * 1000]} {
	error "simpleStructReturnTest: $q * 1000 is not $r(times1000)"
    }
    return "simpleStructReturnTest"
}

proc validate.arrayOfStructsTest {} {
    set max [expr int(rand() * 10) + 2]
    set check 0
    for {set n 0} {$n < $max} {incr n} {
	array set s [stoogeStruct]
	lappend arr [array get s]
	incr check $s(curly)
    }
    set r [arrayOfStructsTest $arr]
    if {$r != $check} {
	error "arrayOfStructsTest failed: $r != $check"
    }
    return "arrayOfStructTest"
}

proc validate.nestedStructTest {} {

    array set s [stoogeStruct]
    set check [expr $s(larry) + $s(moe) + $s(curly)]
    set q    [list \
	         year2000 [list \
		    month03 [list \
		       day01 [stoogeStruct] \
		       day02 [stoogeStruct] \
		       day03 [stoogeStruct]] \
	            month04 [list \
		       day01 [array get s] \
		       day02 [stoogeStruct] \
		       day03 [stoogeStruct]]]]
    
    set r [nestedStructTest $q]
    if {$r != $check} { error "nestedStructTest failed" }
    return "nestedStructTest"
}

# Pestilentially the XMLRPC validator doesn't use the same member names
# as the SOAP validator for this method.
proc validate.xnestedStructTest {} {

    array set s [stoogeStruct]
    set check [expr $s(larry) + $s(moe) + $s(curly)]
    set q    [list \
	         2000 [list \
	            04 [list \
		       01 [array get s] \
                    ] \
	         ] \
	      ]
    
    set r [nestedStructTest $q]
    if {$r != $check} { error "nestedStructTest failed" }
    return "nestedStructTest"
}

# -------------------------------------------------------------------------
    
puts $help

