# (c) 2021 Ashok P. Nadkarni
# See LICENSE for license terms.
#
# Tests for the cffi::type command

source [file join [file dirname [info script]] common.tcl]

namespace eval cffi::test {

    proc setup_enums {} {
        reset_enums
        cffi::enum define ::E {a 0 e 1}
        uplevel #0 [list cffi::enum define X {x 2}]
        cffi::enum define ::ns::E {e 10}
        namespace eval ::ns {cffi::enum define Y {y 20}}
        cffi::enum define ::ns::ns2::E {e 100}
        namespace eval ::ns::ns2 {cffi::enum define Z {z 200}}
    }

    testsubcmd ::cffi::enum
    testnumargs enum-define "cffi::enum define" "ENUM MEMBERS" ""
    testnumargs enum-delete "cffi::enum delete" "PATTERN" ""
    testnumargs enum-list "cffi::enum list" "" "?PATTERN?"
    testnumargs enum-members "cffi::enum members" "ENUM" ""
    testnumargs enum-sequence "cffi::enum sequence" "ENUM MEMBERNAMES" "?START?"
    testnumargs enum-flags "cffi::enum flags" "ENUM FLAGNAMES" ""
    testnumargs enum-value "cffi::enum value" "ENUM MEMBERNAME" "?DEFAULT?"
    testnumargs enum-name "cffi::enum name" "ENUM VALUE" "?DEFAULT?"

    # Only very basic tests. Actual verification is in function.test

    test enum-0 "Define empty enum" -setup {
        reset_enums
    } -body {
        list [cffi::enum define E {}] [cffi::enum members E]
    } -result [list [namespace current]::E {}]

    test enum-1 "Define enum" -setup {
        reset_enums
    } -body {
        list [cffi::enum define E {a 1 b 2}] [cffi::enum members E]
    } -result [list [lprefixns E] {a 1 b 2}]

    test enum-invalid-value-0 "Define enum - invalid value" -body {
        cffi::enum define E {a abc}
    } -result {expected integer but got "abc"} -returnCodes error

    test enum-invalid-name-0 "Define enum - invalid member name" -body {
        cffi::enum define E {1 2}
    } -result {Invalid value "1". Invalid name syntax.} -returnCodes error


    test enum-2 "enum list" -setup {
        reset_enums
        cffi::enum define E {a 1 b 2}
        cffi::enum define E2 {x 1 y 2}
        cffi::enum define X {}
    } -body {
        lsort [cffi::enum list]
    } -result [lprefix [namespace current]:: E E2 X]

    test enum-3 "enum list exact" -setup {
        reset_enums
        cffi::enum define E {a 1 b 2}
        cffi::enum define E2 {x 1 y 2}
        cffi::enum define X {}
    } -body {
        lsort [cffi::enum list E]
    } -result [namespace current]::E

    test enum-4 "enum list glob" -setup {
        reset_enums
        cffi::enum define E {a 1 b 2}
        cffi::enum define E2 {x 1 y 2}
        cffi::enum define X {}
    } -body {
        lsort [cffi::enum list E*]
    } -result [lprefix [namespace current]:: E E2]

    test enum-5 "enum list empty" -setup {
        reset_enums
        cffi::enum define E {a 1 b 2}
        cffi::enum define E2 {x 1 y 2}
        cffi::enum define X {}
    } -body {
        lsort [cffi::enum list Z*]
    } -result {}

    test enum-6 "enum delete" -setup {
        reset_enums
        cffi::enum define E {a 1 b 2}
        cffi::enum define E2 {x 1 y 2}
        cffi::enum define X {}
    } -body {
        cffi::enum delete E
        lsort [cffi::enum list]
    } -result [lprefix [namespace current]:: E2 X]

    test enum-7 "enum delete glob" -setup {
        reset_enums
        cffi::enum define E {a 1 b 2}
        cffi::enum define E2 {x 1 y 2}
        cffi::enum define X {}
    } -body {
        cffi::enum delete E*
        cffi::enum list
    } -result [lprefix [namespace current]:: X]

    test enum-8 "enum delete none" -setup {
        reset_enums
        cffi::enum define E {a 1 b 2}
        cffi::enum define E2 {x 1 y 2}
        cffi::enum define X {}
        cffi::enum list
    } -body {
        cffi::enum delete Z
        lsort [cffi::enum list]
    } -result [lprefix [namespace current]:: E E2 X]

    test enum-9 "Define flags" -setup {
        reset_enums
    } -body {
        cffi::enum flags E {a b c}
        cffi::enum members E
    } -result {a 1 b 2 c 4}

    test enum-10 "Define sequence" -setup {
        reset_enums
    } -body {
        cffi::enum sequence E {a b}
        cffi::enum members E
    } -result {a 0 b 1}

    test enum-11 "Define sequence start" -setup {
        reset_enums
    } -body {
        cffi::enum sequence E {a b} 42
        cffi::enum members E
    } -result {a 42 b 43}

    test enum-12 "Define empty sequence" -setup {
        reset_enums
    } -body {
        cffi::enum sequence E {}
        cffi::enum members E
    } -result {}

    test enum-13 "Define sequence with explicit value" -setup {
        reset_enums
    } -body {
        cffi::enum sequence E {{a -1} b {c 42} d}
        cffi::enum members E
    } -result {a -1 b 0 c 42 d 43}

    test enum-error-0 "Invalid enum" -body {
        cffi::enum define X {a 1 b}
    } -result {missing value to go with key} -returnCodes error

    test enum-error-1 "Invalid member name in definition" -body {
        cffi::enum define X {1 2 3 4}
    } -result {Invalid value "1". Invalid name syntax.} -returnCodes error

    test enum-error-2 "Invalid member name in sequence" -body {
        cffi::enum sequence X {1 2 3 4}
    } -result {Invalid value "1". Invalid enum sequence member definition.} -returnCodes error

    test enum-error-3 "Invalid member name in flags" -body {
        cffi::enum flags X {1 2 3 4}
    } -result {Invalid value "1". Invalid name syntax.} -returnCodes error

    test enum-error-4 "Invalid member value in sequence" -body {
        cffi::enum sequence X {{a x} 2}
    } -result {Invalid value "a x". Invalid enum sequence member definition.} -returnCodes error

    test enum-error-5 "Invalid member value in sequence" -body {
        cffi::enum sequence X {{a 1 2} 2}
    } -result {Invalid value "a 1 2". Invalid enum sequence member definition.} -returnCodes error

    test enum-value-0 "Get first enum member value" -setup {
        reset_enums
        cffi::enum define E {A 1 B 2 C 3}
    } -body {
        cffi::enum value E A
    } -result 1

    test enum-value-1 "Get last enum member value" -setup {
        reset_enums
        cffi::enum define E {A 1 B 2 C 3}
    } -body {
        cffi::enum value E C
    } -result 3
    test enum-value-2 "Get missing enum member value - defaulted" -setup {
        reset_enums
        cffi::enum define E {A 1 B 2 C 3}
    } -body {
        cffi::enum value E D 42
    } -result 42

    test enum-value-error-0 "Get missing enum member value" -setup {
        reset_enums
        namespace eval :: {cffi::enum delete *}
        cffi::enum define E {A 1 B 2 C 3}
    } -body {
        cffi::enum value E D
    } -result "Enum member name \"D\" not found or inaccessible. " -returnCodes error
    test enum-value-error-1 "Non-existing enum" -body {
        cffi::enum value X A
    } -result {Enum "X" not found or inaccessible. } -returnCodes error

    test enum-name-0 "Get first enum member name" -setup {
        reset_enums
        cffi::enum define E {A 1 B 2 C 3}
    } -body {
        cffi::enum name E 1
    } -result A

    test enum-name-1 "Get last enum member name" -setup {
        reset_enums
        cffi::enum define E {A 1 B 2 C 3}
    } -body {
        cffi::enum name E 3
    } -result C

    test enum-name-2 "Get missing enum member name - defaulted" -setup {
        reset_enums
        cffi::enum define E {A 1 B 2 C 3}
    } -body {
        cffi::enum name E 42 unknown
    } -result unknown

    test enum-name-error-0 "Get missing enum member name" -setup {
        reset_enums
        cffi::enum define E {A 1 B 2 C 3}
    } -body {
        cffi::enum name E 4
    } -result "Enum member value not found. " -returnCodes error

    test enum-name-error-1 "Non-existing enum" -body {
        cffi::enum name X 1
    } -result {Enum "X" not found or inaccessible. } -returnCodes error

    ###
    # Scope based tests
    test enum-value-scope-0 "Local scope overrides global" -setup {
        setup_enums
    } -body {
        namespace eval ::ns {cffi::enum value E e}
    } -result 10

    test enum-value-scope-1 "no fallback to global if local enum defined" -setup {
        setup_enums
    } -body {
        namespace eval ::ns {cffi::enum value E a}
    } -result {Enum member name "a" not found or inaccessible. } -returnCodes error

    test enum-value-scope-2 "Fallback to global" -setup {
        setup_enums
    } -body {
        namespace eval ::ns::ns2 {cffi::enum value X x}
    } -result 2

    test enum-value-scope-3 "relative scope" -setup { setup_enums } -body {
        namespace eval ::ns {cffi::enum value ns2::E e}
    } -result 100

    test enum-value-scope-4 "explicit fqn" -setup { setup_enums } -body {
        cffi::enum value ::ns::E e
    } -result 10

    test enum-value-scope-5 "global relative name" -setup { setup_enums } -body {
        cffi::enum value ns::E e
    } -result 10

    test enum-name-scope-0 "Local scope over global" -setup { reset_enums } -body {
        namespace eval :: {cffi::enum define E {a 1 b 2 c 3}}
        namespace eval ::ns {cffi::enum define E {x 1 y 2}}
        namespace eval ::ns {cffi::enum name E 1}
    } -result x

    test enum-name-scope-1 "no fallback to global if local enum defined" -setup {
        setup_enums
    } -body {
        namespace eval ::ns {cffi::enum name E 1}
    } -result {Enum member value not found. } -returnCodes error

    test enum-name-scope-2 "scope falls back to global" -setup { setup_enums } -body {
        namespace eval ::ns {cffi::enum name X 2}
    } -result x

    test enum-name-scope-3 "relative scope" -setup { setup_enums } -body {
        namespace eval ::ns {cffi::enum name ns2::E 100}
    } -result e

    test enum-name-scope-4 "explicit fqn" -setup { setup_enums } -body {
        cffi::enum name ::ns::E 10
    } -result e

    test enum-name-scope-5 "global relative name" -setup { setup_enums } -body {
        cffi::enum name ns::E 10
    } -result e

    test enum-list-scope-0 "enum list in scope" -setup {setup_enums} -body {
        list \
            [lsort [namespace eval :: {cffi::enum list}]] \
            [lsort [namespace eval ::ns {cffi::enum list}]] \
            [lsort [namespace eval ::ns::ns2 {cffi::enum list}]]
    } -result {{::E ::X} {::ns::E ::ns::Y} {::ns::ns2::E ::ns::ns2::Z}}

    test enum-list-scope-1 "enum list in explicit" -setup {setup_enums} -body {
        cffi::enum list ::ns::*
    } -result {::ns::Y ::ns::E}

    test enum-members-scope-0 "enum members in scope" -setup {
        setup_enums
    } -body {
        namespace eval ::ns {cffi::enum members E}
    } -result {e 10}

    test enum-members-scope-1 "enum members in global scope default" -setup {
        setup_enums
    } -body {
        namespace eval ::ns {cffi::enum members X}
    } -result {x 2}

    test enum-members-scope-2 "enum members relative" -setup {
        setup_enums
    } -body {
        namespace eval ::ns {cffi::enum members ns2::E}
    } -result {e 100}

    test enum-members-scope-3 "enum members in fqn" -setup {
        setup_enums
    } -body {
        cffi::enum members ::ns::E
    } -result {e 10}

    test enum-members-scope-4 "enum members in global relative" -setup {
        setup_enums
    } -body {
        cffi::enum members ns::E
    } -result {e 10}

    test enum-sequence-scope-0 "Local scope sequence" -setup {
        reset_enums
    } -body {
        namespace eval ::ns {cffi::enum sequence E {a b}}
        list [cffi::enum list ::ns::*] [cffi::enum members ::ns::E]
    } -result {::ns::E {a 0 b 1}}

    test enum-sequence-scope-1 "fqn scope sequence" -setup {
        reset_enums
    } -body {
        cffi::enum sequence ::ns::E {a b}
        list [namespace eval ::ns {cffi::enum list}] [cffi::enum members ::ns::E]
    } -result {::ns::E {a 0 b 1}}

    test enum-sequence-scope-2 "relative scope sequence" -setup {
        reset_enums
    } -body {
        namespace eval ::ns {cffi::enum sequence ns2::E {a b}}
        list [cffi::enum list ::ns::ns2::*] [cffi::enum members ::ns::ns2::E]
    } -result {::ns::ns2::E {a 0 b 1}}

    test enum-flags-scope-0 "Local scope flags" -setup {
        reset_enums
    } -body {
        namespace eval ::ns {cffi::enum flags E {a b}}
        list [cffi::enum list ::ns::*] [cffi::enum members ::ns::E]
    } -result {::ns::E {a 1 b 2}}

    test enum-flags-scope-1 "fqn scope flags" -setup {
        reset_enums
    } -body {
        cffi::enum flags ::ns::E {a b}
        list [cffi::enum list ::ns::*] [cffi::enum members ::ns::E]
    } -result {::ns::E {a 1 b 2}}

    test enum-flags-scope-2 "relative scope flags" -setup {
        reset_enums
    } -body {
        namespace eval ::ns {cffi::enum flags ns2::E {a b}}
        list [cffi::enum list ::*] [cffi::enum list ::ns::*] [cffi::enum list ::ns::ns2::*] [cffi::enum members ::ns::ns2::E]
    } -result {{} {} ::ns::ns2::E {a 1 b 2}}

    test enum-delete-scope-0 "Local scope delete" -setup {
        reset_enums
    } -body {
        namespace eval :: {cffi::enum define G {a 1 b 2 c 3}}
        namespace eval ::ns {cffi::enum define L {a 0 b 1}}
        list \
            [lsort [namespace eval :: {cffi::enum list}]] \
            [namespace eval ::ns {cffi::enum list}] \
            [namespace eval ::ns {cffi::enum delete *}] \
            [namespace eval :: {cffi::enum list}] \
            [namespace eval ::ns {cffi::enum list}]
    } -result {::G ::ns::L {} ::G {}}

    test enum-mask-0 "enum mask" -setup {
        reset_enums
        cffi::enum flags E {a b c}
    } -body {
        cffi::enum mask E {a c}
    } -result 5
    test enum-mask-1 "enum mask - empty" -setup {
        reset_enums
        cffi::enum flags E {a b c}
    } -body {
        cffi::enum mask E {}
    } -result 0
    test enum-mask-2 "enum mask - number" -setup {
        reset_enums
        cffi::enum flags E {a b c}
    } -body {
        cffi::enum mask E {a 16}
    } -result 17

    test enum-unmask-0 "enum unmask" -setup {
        reset_enums
        cffi::enum flags E {a b c}
    } -body {
        cffi::enum unmask E 5
    } -result {a c 5}
    test enum-unmask-1 "enum unmask 0" -setup {
        reset_enums
        cffi::enum flags E {a b c}
    } -body {
        cffi::enum unmask E 0
    } -result {0}
    test enum-unmask-2 "enum unmask leftover bits" -setup {
        reset_enums
        cffi::enum flags E {a b c}
    } -body {
        cffi::enum unmask E 25
    } -result {a 25}

    ###

    test enum-clear-0 "Erase all enums" -setup { setup_enums } -body {
        set enums_pre [list [cffi::enum list ::*] [cffi::enum list ::ns::*] [cffi::enum list ::ns::ns2::*]]
        cffi::enum clear
        set enums_post [list [cffi::enum list ::*] [cffi::enum list ::ns::*] [cffi::enum list ::ns::ns2::*]]
        list $enums_pre $enums_post
    } -result {{{::X ::E} {::ns::Y ::ns::E} {::ns::ns2::E ::ns::ns2::Z}} {{} {} {}}}
}

::tcltest::cleanupTests
namespace delete cffi::test
