using CBinding
using Random: RandomDevice

# Automatically generated using Clang.jl

const NOT_SPECIAL = 0
const PSEUDO_MERSENNE = 1
const MONTGOMERY_FRIENDLY = 3
const GENERALISED_MERSENNE = 2
const WEIERSTRASS = 0
const EDWARDS = 1
const MONTGOMERY = 2
const NOT = 0
const BN = 1
const BLS = 2
const D_TYPE = 0
const M_TYPE = 1
const AMCL_FP_ZERO = 0
const AMCL_FP_UNITY = 1
const AMCL_FP_SPARSER = 2
const AMCL_FP_SPARSE = 3
const AMCL_FP_DENSE = 4
const SHA256 = 32
const SHA384 = 48
const SHA512 = 64
const SHA3_HASH224 = 28
const SHA3_HASH256 = 32
const SHA3_HASH384 = 48
const SHA3_HASH512 = 64
const SHAKE128 = 16
const SHAKE256 = 32
const RLWE_PRIME = 0x3001
const RLWE_LGN = 10
const RLWE_ND = Float32(0x0f7002ff)
const RLWE_ONE = 0x2ac8
const RLWE_R2MODP = 0x1620
const ECB = 0
const CBC = 1
const CFB1 = 2
const CFB2 = 3
const CFB4 = 5
const OFB1 = 14
const OFB2 = 15
const OFB4 = 17
const OFB8 = 21
const OFB16 = 29
const CTR1 = 30
const CTR2 = 31
const CTR4 = 33
const CTR8 = 37
const CTR16 = 45

# Skipping MacroDefinition: uchar unsigned char

const GCM_ACCEPTING_HEADER = 0
const GCM_ACCEPTING_CIPHER = 1
const GCM_NOT_ACCEPTING_MORE = 2
const GCM_FINISHED = 3
const GCM_ENCRYPTING = 0
const GCM_DECRYPTING = 1
const NK = 21
const NJ = 6
const NV = 8

@cstruct hash256 {
    length::UInt32[2]
    h::UInt32[8]
    w::UInt32[80]
    hlen::Cint
}
function hash256()
    h = hash256(undef)
    HASH256_init(h)
    return h
end

@cstruct hash512 {
    length::UInt64[2]
    h::UInt64[8]
    w::UInt64[80]
    hlen::Cint
}
function hash512()
    h = hash512(undef)
    HASH512_init(h)
    return h
end

const hash384 = hash512

@cstruct sha3 {
    length::UInt64
    S::UInt64[5][5]
    rate::Cint
    len::Cint
}
function sha3(type::Int)
    s = sha3(undef)
    SHA3_init(s, type)
    return s
end

@cstruct amcl_aes {
    Nk::Cint
    Nr::Cint
    mode::Cint
    fkey::UInt32[60]
    rkey::UInt32[60]
    f::UInt8[16]
}
function amcl_aes(mode, key, iv)
    aes = amcl_aes(undef)
    res = AES_init(aes, mode, length(key), pointer(key), pointer(iv))
    res == 1 || error("failed to initialize amcl_aes struct ($res)")
    return aes
end

@cstruct gcm {
    table::UInt32[128][4]
    stateX::Cuchar[16]
    Y_0::Cuchar[16]
    lenA::UInt32[2]
    lenC::UInt32[2]
    status::Cint
    a::amcl_aes
}
function gcm(key, n, iv)
    g = gcm(undef)
    GCM_init(g, length(key), pointer(key), n, pointer(iv))
    return g
end

mutable struct octet{N}
    len::Cint
    max::Cint
    val::Ptr{UInt8}
    data::NTuple{N, UInt8}
    octet(n::Int) = octet(zeros(UInt8, n))
    function octet(data)
        n = sizeof(data)
        o = new{n}(n, n)
        # offset is asserted in __init__
        o.val = pointer_from_objref(o) + fieldoffset(octet{0}, 4)
        # significantly faster than the safe variant
        unsafe_copyto!(reinterpret(Ptr{UInt8}, o.val), reinterpret(Ptr{UInt8}, pointer(data)), n)
        return o
    end
end

@cstruct csprng {
    ira::UInt32[21]
    rndptr::Cint
    borrow::UInt32
    pool_ptr::Cint
    pool::UInt8[32]
}
function csprng(seed::octet)
    rng = csprng(undef)
    CREATE_CSPRNG(rng, seed)
    return rng
end
function csprng()
    rng = RandomDevice()
    try # this will not work on Windows
        rng = RandomDevice(unlimited=false)
        @debug "reading seed from /dev/random (this might block for a while) ..."
    catch e
        e isa MethodError || rethrow(e)
    end
    seed = rand(rng, UInt8, 32) # 256-bit seed
    @debug "got seed from OS"
    return csprng(octet(seed))
end

const CHUNK = 64
const byte = uint8_t
const sign8 = int8_t
const sign16 = int16_t
const sign32 = int32_t
const sign64 = int64_t
const unsign32 = uint32_t
const unsign64 = uint64_t
const chunk = int64_t
const MODBYTES_1024_58 = 128
const BIGBITS_1024_58 = 8MODBYTES_1024_58

const BASEBITS_1024_58 = 58

# Skipping MacroDefinition: NLEN_1024_58 ( 1 + ( ( 8 * MODBYTES_1024_58 - 1 ) / BASEBITS_1024_58 ) )
const NLEN_1024_58 = ( 1 + ( ( 8 * MODBYTES_1024_58 - 1 ) / BASEBITS_1024_58 ) )
const DNLEN_1024_58 = 2NLEN_1024_58

# Skipping MacroDefinition: BMASK_1024_58 ( ( ( chunk ) 1 << BASEBITS_1024_58 ) - 1 )
# Skipping MacroDefinition: NEXCESS_1024_58 ( 1 << ( CHUNK - BASEBITS_1024_58 - 1 ) )

const HBITS_1024_58 = BASEBITS_1024_58 / 2

# Skipping MacroDefinition: HMASK_1024_58 ( ( ( chunk ) 1 << HBITS_1024_58 ) - 1 )

@cstruct BIG_1024_58 { data::Int64[18] }
@cstruct DBIG_1024_58 { data::Int64[36] }
const MODBYTES_256_56 = 32
const BIGBITS_256_56 = 8MODBYTES_256_56

const BASEBITS_256_56 = 56

# Skipping MacroDefinition: NLEN_256_56 ( 1 + ( ( 8 * MODBYTES_256_56 - 1 ) / BASEBITS_256_56 ) )
const NLEN_256_56 = ( 1 + ( ( 8 * MODBYTES_256_56 - 1 ) / BASEBITS_256_56 ) )
const DNLEN_256_56 = 2NLEN_256_56

# Skipping MacroDefinition: BMASK_256_56 ( ( ( chunk ) 1 << BASEBITS_256_56 ) - 1 )
# Skipping MacroDefinition: NEXCESS_256_56 ( 1 << ( CHUNK - BASEBITS_256_56 - 1 ) )

const HBITS_256_56 = BASEBITS_256_56 / 2

# Skipping MacroDefinition: HMASK_256_56 ( ( ( chunk ) 1 << HBITS_256_56 ) - 1 )

@cstruct BIG_256_56 { data::Int64[5] }
@cstruct DBIG_256_56 { data::Int64[10] }
const MODBYTES_336_60 = 42
const BIGBITS_336_60 = 8MODBYTES_336_60

const BASEBITS_336_60 = 60

# Skipping MacroDefinition: NLEN_336_60 ( 1 + ( ( 8 * MODBYTES_336_60 - 1 ) / BASEBITS_336_60 ) )
const NLEN_336_60 = ( 1 + ( ( 8 * MODBYTES_336_60 - 1 ) / BASEBITS_336_60 ) )
const DNLEN_336_60 = 2NLEN_336_60

# Skipping MacroDefinition: BMASK_336_60 ( ( ( chunk ) 1 << BASEBITS_336_60 ) - 1 )
# Skipping MacroDefinition: NEXCESS_336_60 ( 1 << ( CHUNK - BASEBITS_336_60 - 1 ) )

const HBITS_336_60 = BASEBITS_336_60 / 2

# Skipping MacroDefinition: HMASK_336_60 ( ( ( chunk ) 1 << HBITS_336_60 ) - 1 )

@cstruct BIG_336_60 { data::Int64[6] }
@cstruct DBIG_336_60 { data::Int64[12] }
const MODBYTES_384_56 = 48
const BIGBITS_384_56 = 8MODBYTES_384_56

const BASEBITS_384_56 = 56

# Skipping MacroDefinition: NLEN_384_56 ( 1 + ( ( 8 * MODBYTES_384_56 - 1 ) / BASEBITS_384_56 ) )
const NLEN_384_56 = ( 1 + ( ( 8 * MODBYTES_384_56 - 1 ) / BASEBITS_384_56 ) )
const DNLEN_384_56 = 2NLEN_384_56

# Skipping MacroDefinition: BMASK_384_56 ( ( ( chunk ) 1 << BASEBITS_384_56 ) - 1 )
# Skipping MacroDefinition: NEXCESS_384_56 ( 1 << ( CHUNK - BASEBITS_384_56 - 1 ) )

const HBITS_384_56 = BASEBITS_384_56 / 2

# Skipping MacroDefinition: HMASK_384_56 ( ( ( chunk ) 1 << HBITS_384_56 ) - 1 )

@cstruct BIG_384_56 { data::Int64[7] }
@cstruct DBIG_384_56 { data::Int64[14] }
const MODBYTES_384_58 = 48
const BIGBITS_384_58 = 8MODBYTES_384_58

const BASEBITS_384_58 = 58

# Skipping MacroDefinition: NLEN_384_58 ( 1 + ( ( 8 * MODBYTES_384_58 - 1 ) / BASEBITS_384_58 ) )
const NLEN_384_58 = ( 1 + ( ( 8 * MODBYTES_384_58 - 1 ) / BASEBITS_384_58 ) )
const DNLEN_384_58 = 2NLEN_384_58

# Skipping MacroDefinition: BMASK_384_58 ( ( ( chunk ) 1 << BASEBITS_384_58 ) - 1 )
# Skipping MacroDefinition: NEXCESS_384_58 ( 1 << ( CHUNK - BASEBITS_384_58 - 1 ) )

const HBITS_384_58 = BASEBITS_384_58 / 2

# Skipping MacroDefinition: HMASK_384_58 ( ( ( chunk ) 1 << HBITS_384_58 ) - 1 )

@cstruct BIG_384_58 { data::Int64[7] }
@cstruct DBIG_384_58 { data::Int64[14] }
const MODBYTES_416_60 = 52
const BIGBITS_416_60 = 8MODBYTES_416_60

const BASEBITS_416_60 = 60

# Skipping MacroDefinition: NLEN_416_60 ( 1 + ( ( 8 * MODBYTES_416_60 - 1 ) / BASEBITS_416_60 ) )
const NLEN_416_60 = ( 1 + ( ( 8 * MODBYTES_416_60 - 1 ) / BASEBITS_416_60 ) )
const DNLEN_416_60 = 2NLEN_416_60

# Skipping MacroDefinition: BMASK_416_60 ( ( ( chunk ) 1 << BASEBITS_416_60 ) - 1 )
# Skipping MacroDefinition: NEXCESS_416_60 ( 1 << ( CHUNK - BASEBITS_416_60 - 1 ) )

const HBITS_416_60 = BASEBITS_416_60 / 2

# Skipping MacroDefinition: HMASK_416_60 ( ( ( chunk ) 1 << HBITS_416_60 ) - 1 )

@cstruct BIG_416_60 { data::Int64[7] }
@cstruct DBIG_416_60 { data::Int64[14] }
const MODBYTES_448_58 = 56
const BIGBITS_448_58 = 8MODBYTES_448_58

const BASEBITS_448_58 = 58

# Skipping MacroDefinition: NLEN_448_58 ( 1 + ( ( 8 * MODBYTES_448_58 - 1 ) / BASEBITS_448_58 ) )
const NLEN_448_58 = ( 1 + ( ( 8 * MODBYTES_448_58 - 1 ) / BASEBITS_448_58 ) )
const DNLEN_448_58 = 2NLEN_448_58

# Skipping MacroDefinition: BMASK_448_58 ( ( ( chunk ) 1 << BASEBITS_448_58 ) - 1 )
# Skipping MacroDefinition: NEXCESS_448_58 ( 1 << ( CHUNK - BASEBITS_448_58 - 1 ) )

const HBITS_448_58 = BASEBITS_448_58 / 2

# Skipping MacroDefinition: HMASK_448_58 ( ( ( chunk ) 1 << HBITS_448_58 ) - 1 )

@cstruct BIG_448_58 { data::Int64[8] }
@cstruct DBIG_448_58 { data::Int64[16] }
const MODBYTES_464_60 = 58
const BIGBITS_464_60 = 8MODBYTES_464_60

const BASEBITS_464_60 = 60

# Skipping MacroDefinition: NLEN_464_60 ( 1 + ( ( 8 * MODBYTES_464_60 - 1 ) / BASEBITS_464_60 ) )
const NLEN_464_60 = ( 1 + ( ( 8 * MODBYTES_464_60 - 1 ) / BASEBITS_464_60 ) )
const DNLEN_464_60 = 2NLEN_464_60

# Skipping MacroDefinition: BMASK_464_60 ( ( ( chunk ) 1 << BASEBITS_464_60 ) - 1 )
# Skipping MacroDefinition: NEXCESS_464_60 ( 1 << ( CHUNK - BASEBITS_464_60 - 1 ) )

const HBITS_464_60 = BASEBITS_464_60 / 2

# Skipping MacroDefinition: HMASK_464_60 ( ( ( chunk ) 1 << HBITS_464_60 ) - 1 )

@cstruct BIG_464_60 { data::Int64[8] }
@cstruct DBIG_464_60 { data::Int64[16] }
const MODBYTES_480_56 = 60
const BIGBITS_480_56 = 8MODBYTES_480_56

const BASEBITS_480_56 = 56

# Skipping MacroDefinition: NLEN_480_56 ( 1 + ( ( 8 * MODBYTES_480_56 - 1 ) / BASEBITS_480_56 ) )
const NLEN_480_56 = ( 1 + ( ( 8 * MODBYTES_480_56 - 1 ) / BASEBITS_480_56 ) )
const DNLEN_480_56 = 2NLEN_480_56

# Skipping MacroDefinition: BMASK_480_56 ( ( ( chunk ) 1 << BASEBITS_480_56 ) - 1 )
# Skipping MacroDefinition: NEXCESS_480_56 ( 1 << ( CHUNK - BASEBITS_480_56 - 1 ) )

const HBITS_480_56 = BASEBITS_480_56 / 2

# Skipping MacroDefinition: HMASK_480_56 ( ( ( chunk ) 1 << HBITS_480_56 ) - 1 )

@cstruct BIG_480_56 { data::Int64[9] }
@cstruct DBIG_480_56 { data::Int64[18] }
const MODBYTES_512_56 = 64
const BIGBITS_512_56 = 8MODBYTES_512_56

const BASEBITS_512_56 = 56

# Skipping MacroDefinition: NLEN_512_56 ( 1 + ( ( 8 * MODBYTES_512_56 - 1 ) / BASEBITS_512_56 ) )
const NLEN_512_56 = ( 1 + ( ( 8 * MODBYTES_512_56 - 1 ) / BASEBITS_512_56 ) )
const DNLEN_512_56 = 2NLEN_512_56

# Skipping MacroDefinition: BMASK_512_56 ( ( ( chunk ) 1 << BASEBITS_512_56 ) - 1 )
# Skipping MacroDefinition: NEXCESS_512_56 ( 1 << ( CHUNK - BASEBITS_512_56 - 1 ) )

const HBITS_512_56 = BASEBITS_512_56 / 2

# Skipping MacroDefinition: HMASK_512_56 ( ( ( chunk ) 1 << HBITS_512_56 ) - 1 )

@cstruct BIG_512_56 { data::Int64[10] }
@cstruct DBIG_512_56 { data::Int64[20] }
const MODBYTES_512_60 = 64
const BIGBITS_512_60 = 8MODBYTES_512_60

const BASEBITS_512_60 = 60

# Skipping MacroDefinition: NLEN_512_60 ( 1 + ( ( 8 * MODBYTES_512_60 - 1 ) / BASEBITS_512_60 ) )
const NLEN_512_60 = ( 1 + ( ( 8 * MODBYTES_512_60 - 1 ) / BASEBITS_512_60 ) )
const DNLEN_512_60 = 2NLEN_512_60

# Skipping MacroDefinition: BMASK_512_60 ( ( ( chunk ) 1 << BASEBITS_512_60 ) - 1 )
# Skipping MacroDefinition: NEXCESS_512_60 ( 1 << ( CHUNK - BASEBITS_512_60 - 1 ) )

const HBITS_512_60 = BASEBITS_512_60 / 2

# Skipping MacroDefinition: HMASK_512_60 ( ( ( chunk ) 1 << HBITS_512_60 ) - 1 )

@cstruct BIG_512_60 { data::Int64[9] }
@cstruct DBIG_512_60 { data::Int64[18] }
const MODBYTES_528_60 = 66
const BIGBITS_528_60 = 8MODBYTES_528_60

const BASEBITS_528_60 = 60

# Skipping MacroDefinition: NLEN_528_60 ( 1 + ( ( 8 * MODBYTES_528_60 - 1 ) / BASEBITS_528_60 ) )
const NLEN_528_60 = ( 1 + ( ( 8 * MODBYTES_528_60 - 1 ) / BASEBITS_528_60 ) )
const DNLEN_528_60 = 2NLEN_528_60

# Skipping MacroDefinition: BMASK_528_60 ( ( ( chunk ) 1 << BASEBITS_528_60 ) - 1 )
# Skipping MacroDefinition: NEXCESS_528_60 ( 1 << ( CHUNK - BASEBITS_528_60 - 1 ) )

const HBITS_528_60 = BASEBITS_528_60 / 2

# Skipping MacroDefinition: HMASK_528_60 ( ( ( chunk ) 1 << HBITS_528_60 ) - 1 )

@cstruct BIG_528_60 { data::Int64[9] }
@cstruct DBIG_528_60 { data::Int64[18] }
const MODBYTES_560_58 = 70
const BIGBITS_560_58 = 8MODBYTES_560_58

const BASEBITS_560_58 = 58

# Skipping MacroDefinition: NLEN_560_58 ( 1 + ( ( 8 * MODBYTES_560_58 - 1 ) / BASEBITS_560_58 ) )
const NLEN_560_58 = ( 1 + ( ( 8 * MODBYTES_560_58 - 1 ) / BASEBITS_560_58 ) )
const DNLEN_560_58 = 2NLEN_560_58

# Skipping MacroDefinition: BMASK_560_58 ( ( ( chunk ) 1 << BASEBITS_560_58 ) - 1 )
# Skipping MacroDefinition: NEXCESS_560_58 ( 1 << ( CHUNK - BASEBITS_560_58 - 1 ) )

const HBITS_560_58 = BASEBITS_560_58 / 2

# Skipping MacroDefinition: HMASK_560_58 ( ( ( chunk ) 1 << HBITS_560_58 ) - 1 )

@cstruct BIG_560_58 { data::Int64[10] }
@cstruct DBIG_560_58 { data::Int64[20] }
const BGS_BLS24 = MODBYTES_480_56
const BFS_BLS24 = MODBYTES_480_56
const BLS_OK = 0
const BLS_FAIL = 41
const BLS_INVALID_G1 = 42
const BLS_INVALID_G2 = 43
const BGS_BLS48 = MODBYTES_560_58
const BFS_BLS48 = MODBYTES_560_58
const BGS_BLS381 = MODBYTES_384_58
const BFS_BLS381 = MODBYTES_384_58
const BGS_BLS383 = MODBYTES_384_58
const BFS_BLS383 = MODBYTES_384_58
const BGS_BLS461 = MODBYTES_464_60
const BFS_BLS461 = MODBYTES_464_60
const BGS_BN254 = MODBYTES_256_56
const BFS_BN254 = MODBYTES_256_56
const BGS_BN254CX = MODBYTES_256_56
const BFS_BN254CX = MODBYTES_256_56
const BGS_FP256BN = MODBYTES_256_56
const BFS_FP256BN = MODBYTES_256_56
const BGS_FP512BN = MODBYTES_512_60
const BFS_FP512BN = MODBYTES_512_60
const CURVETYPE_ANSSI = WEIERSTRASS
const PAIRING_FRIENDLY_ANSSI = NOT
const CURVE_SECURITY_ANSSI = 128
const AESKEY_ANSSI = 16
const HASH_TYPE_ANSSI = SHA256
const CURVETYPE_BLS24 = WEIERSTRASS
const PAIRING_FRIENDLY_BLS24 = BLS
const CURVE_SECURITY_BLS24 = 192
const POSITIVEX = 0
const NEGATIVEX = 1
const SEXTIC_TWIST_BLS24 = M_TYPE
const SIGN_OF_X_BLS24 = POSITIVEX
const AESKEY_BLS24 = 24
const HASH_TYPE_BLS24 = SHA384
const CURVETYPE_BLS381 = WEIERSTRASS
const PAIRING_FRIENDLY_BLS381 = BLS
const CURVE_SECURITY_BLS381 = 128
const SEXTIC_TWIST_BLS381 = M_TYPE
const SIGN_OF_X_BLS381 = NEGATIVEX
const AESKEY_BLS381 = 16
const HASH_TYPE_BLS381 = SHA256
const CURVETYPE_BLS383 = WEIERSTRASS
const PAIRING_FRIENDLY_BLS383 = BLS
const CURVE_SECURITY_BLS383 = 128
const SEXTIC_TWIST_BLS383 = M_TYPE
const SIGN_OF_X_BLS383 = POSITIVEX
const AESKEY_BLS383 = 16
const HASH_TYPE_BLS383 = SHA256
const CURVETYPE_BLS461 = WEIERSTRASS
const PAIRING_FRIENDLY_BLS461 = BLS
const CURVE_SECURITY_BLS461 = 128
const SEXTIC_TWIST_BLS461 = M_TYPE
const SIGN_OF_X_BLS461 = NEGATIVEX
const AESKEY_BLS461 = 16
const HASH_TYPE_BLS461 = SHA256
const CURVETYPE_BLS48 = WEIERSTRASS
const PAIRING_FRIENDLY_BLS48 = BLS
const CURVE_SECURITY_BLS48 = 256
const SEXTIC_TWIST_BLS48 = M_TYPE
const SIGN_OF_X_BLS48 = POSITIVEX
const AESKEY_BLS48 = 32
const HASH_TYPE_BLS48 = SHA512
const CURVETYPE_BN254 = WEIERSTRASS
const PAIRING_FRIENDLY_BN254 = BN
const CURVE_SECURITY_BN254 = 128
const SEXTIC_TWIST_BN254 = D_TYPE
const SIGN_OF_X_BN254 = NEGATIVEX
const AESKEY_BN254 = 16
const HASH_TYPE_BN254 = SHA256
const CURVETYPE_BN254CX = WEIERSTRASS
const PAIRING_FRIENDLY_BN254CX = BN
const CURVE_SECURITY_BN254CX = 128
const SEXTIC_TWIST_BN254CX = D_TYPE
const SIGN_OF_X_BN254CX = NEGATIVEX
const AESKEY_BN254CX = 16
const HASH_TYPE_BN254CX = SHA256
const CURVETYPE_BRAINPOOL = WEIERSTRASS
const PAIRING_FRIENDLY_BRAINPOOL = NOT
const CURVE_SECURITY_BRAINPOOL = 128
const AESKEY_BRAINPOOL = 16
const HASH_TYPE_BRAINPOOL = SHA256
const CURVETYPE_C25519 = MONTGOMERY
const PAIRING_FRIENDLY_C25519 = NOT
const CURVE_SECURITY_C25519 = 128
const AESKEY_C25519 = 16
const HASH_TYPE_C25519 = SHA256
const CURVETYPE_C41417 = EDWARDS
const PAIRING_FRIENDLY_C41417 = NOT
const CURVE_SECURITY_C41417 = 128
const AESKEY_C41417 = 16
const HASH_TYPE_C41417 = SHA256
const CURVETYPE_ED25519 = EDWARDS
const PAIRING_FRIENDLY_ED25519 = NOT
const CURVE_SECURITY_ED25519 = 128
const AESKEY_ED25519 = 16
const HASH_TYPE_ED25519 = SHA256
const CURVETYPE_FP256BN = WEIERSTRASS
const PAIRING_FRIENDLY_FP256BN = BN
const CURVE_SECURITY_FP256BN = 128
const SEXTIC_TWIST_FP256BN = M_TYPE
const SIGN_OF_X_FP256BN = NEGATIVEX
const AESKEY_FP256BN = 16
const HASH_TYPE_FP256BN = SHA256
const CURVETYPE_FP512BN = WEIERSTRASS
const PAIRING_FRIENDLY_FP512BN = BN
const CURVE_SECURITY_FP512BN = 128
const SEXTIC_TWIST_FP512BN = M_TYPE
const SIGN_OF_X_FP512BN = POSITIVEX
const AESKEY_FP512BN = 16
const HASH_TYPE_FP512BN = SHA256
const CURVETYPE_GOLDILOCKS = EDWARDS
const PAIRING_FRIENDLY_GOLDILOCKS = NOT
const CURVE_SECURITY_GOLDILOCKS = 128
const AESKEY_GOLDILOCKS = 16
const HASH_TYPE_GOLDILOCKS = SHA256
const CURVETYPE_HIFIVE = EDWARDS
const PAIRING_FRIENDLY_HIFIVE = NOT
const CURVE_SECURITY_HIFIVE = 128
const AESKEY_HIFIVE = 16
const HASH_TYPE_HIFIVE = SHA256
const CURVETYPE_NIST256 = WEIERSTRASS
const PAIRING_FRIENDLY_NIST256 = NOT
const CURVE_SECURITY_NIST256 = 128
const AESKEY_NIST256 = 16
const HASH_TYPE_NIST256 = SHA256
const CURVETYPE_NIST384 = WEIERSTRASS
const PAIRING_FRIENDLY_NIST384 = NOT
const CURVE_SECURITY_NIST384 = 128
const AESKEY_NIST384 = 16
const HASH_TYPE_NIST384 = SHA256
const CURVETYPE_NIST521 = WEIERSTRASS
const PAIRING_FRIENDLY_NIST521 = NOT
const CURVE_SECURITY_NIST521 = 128
const AESKEY_NIST521 = 16
const HASH_TYPE_NIST521 = SHA256
const CURVETYPE_NUMS256E = EDWARDS
const PAIRING_FRIENDLY_NUMS256E = NOT
const CURVE_SECURITY_NUMS256E = 128
const AESKEY_NUMS256E = 16
const HASH_TYPE_NUMS256E = SHA256
const CURVETYPE_NUMS256W = WEIERSTRASS
const PAIRING_FRIENDLY_NUMS256W = NOT
const CURVE_SECURITY_NUMS256W = 128
const AESKEY_NUMS256W = 16
const HASH_TYPE_NUMS256W = SHA256
const CURVETYPE_NUMS384E = EDWARDS
const PAIRING_FRIENDLY_NUMS384E = NOT
const CURVE_SECURITY_NUMS384E = 128
const AESKEY_NUMS384E = 16
const HASH_TYPE_NUMS384E = SHA256
const CURVETYPE_NUMS384W = WEIERSTRASS
const PAIRING_FRIENDLY_NUMS384W = NOT
const CURVE_SECURITY_NUMS384W = 128
const AESKEY_NUMS384W = 16
const HASH_TYPE_NUMS384W = SHA256
const CURVETYPE_NUMS512E = EDWARDS
const PAIRING_FRIENDLY_NUMS512E = NOT
const CURVE_SECURITY_NUMS512E = 128
const AESKEY_NUMS512E = 16
const HASH_TYPE_NUMS512E = SHA256
const CURVETYPE_NUMS512W = WEIERSTRASS
const PAIRING_FRIENDLY_NUMS512W = NOT
const CURVE_SECURITY_NUMS512W = 128
const AESKEY_NUMS512W = 16
const HASH_TYPE_NUMS512W = SHA256
const CURVETYPE_SECP256K1 = WEIERSTRASS
const PAIRING_FRIENDLY_SECP256K1 = NOT
const CURVE_SECURITY_SECP256K1 = 128
const AESKEY_SECP256K1 = 16
const HASH_TYPE_SECP256K1 = SHA256
const FFLEN_2048 = 2
const FFLEN_3072 = 8
const FFLEN_4096 = 8
const MBITS_25519 = 255
const MOD8_25519 = 5
const MODTYPE_25519 = PSEUDO_MERSENNE
const MAXXES_25519 = 25
const MBITS_256PME = 256
const MOD8_256PME = 3
const MODTYPE_256PME = PSEUDO_MERSENNE
const MAXXES_256PME = 24
const MBITS_256PMW = 256
const MOD8_256PMW = 3
const MODTYPE_256PMW = PSEUDO_MERSENNE
const MAXXES_256PMW = 24
const MBITS_384PM = 384
const MOD8_384PM = 3
const MODTYPE_384PM = PSEUDO_MERSENNE
const MAXXES_384PM = 8
const MBITS_512PM = 512
const MOD8_512PM = 7
const MODTYPE_512PM = PSEUDO_MERSENNE
const MAXXES_512PM = 30
const MBITS_ANSSI = 256
const MOD8_ANSSI = 7
const MODTYPE_ANSSI = NOT_SPECIAL
const MAXXES_ANSSI = 24
const MBITS_BLS24 = 479
const MOD8_BLS24 = 3
const MODTYPE_BLS24 = NOT_SPECIAL
const MAXXES_BLS24 = 25
const MBITS_BLS381 = 381
const MOD8_BLS381 = 3
const MODTYPE_BLS381 = NOT_SPECIAL
const MAXXES_BLS381 = 25
const MBITS_BLS383 = 383
const MOD8_BLS383 = 3
const MODTYPE_BLS383 = NOT_SPECIAL
const MAXXES_BLS383 = 23
const MBITS_BLS461 = 461
const MOD8_BLS461 = 3
const MODTYPE_BLS461 = NOT_SPECIAL
const MAXXES_BLS461 = 19
const MBITS_BLS48 = 556
const MOD8_BLS48 = 3
const MODTYPE_BLS48 = NOT_SPECIAL
const MAXXES_BLS48 = 24
const MBITS_BN254 = 254
const MOD8_BN254 = 3
const MODTYPE_BN254 = NOT_SPECIAL
const MAXXES_BN254 = 26
const MBITS_BN254CX = 254
const MOD8_BN254CX = 3
const MODTYPE_BN254CX = NOT_SPECIAL
const MAXXES_BN254CX = 26
const MBITS_BRAINPOOL = 256
const MOD8_BRAINPOOL = 7
const MODTYPE_BRAINPOOL = NOT_SPECIAL
const MAXXES_BRAINPOOL = 24
const MBITS_C41417 = 414
const MOD8_C41417 = 7
const MODTYPE_C41417 = PSEUDO_MERSENNE
const MAXXES_C41417 = 6
const MBITS_FP256BN = 256
const MOD8_FP256BN = 3
const MODTYPE_FP256BN = NOT_SPECIAL
const MAXXES_FP256BN = 24
const MBITS_FP512BN = 512
const MOD8_FP512BN = 3
const MODTYPE_FP512BN = NOT_SPECIAL
const MAXXES_FP512BN = 28
const MBITS_GOLDILOCKS = 448
const MOD8_GOLDILOCKS = 7
const MODTYPE_GOLDILOCKS = GENERALISED_MERSENNE
const MAXXES_GOLDILOCKS = 16
const MBITS_HIFIVE = 336
const MOD8_HIFIVE = 5
const MODTYPE_HIFIVE = PSEUDO_MERSENNE
const MAXXES_HIFIVE = 24
const MBITS_NIST256 = 256
const MOD8_NIST256 = 7
const MODTYPE_NIST256 = NOT_SPECIAL
const MAXXES_NIST256 = 24
const MBITS_NIST384 = 384
const MOD8_NIST384 = 7
const MODTYPE_NIST384 = NOT_SPECIAL
const MAXXES_NIST384 = 8
const MBITS_NIST521 = 521
const MOD8_NIST521 = 7
const MODTYPE_NIST521 = PSEUDO_MERSENNE
const MAXXES_NIST521 = 19
const MBITS_SECP256K1 = 256
const MOD8_SECP256K1 = 7
const MODTYPE_SECP256K1 = NOT_SPECIAL
const MAXXES_SECP256K1 = 24
const EGS_ANSSI = MODBYTES_256_56
const EFS_ANSSI = MODBYTES_256_56
const ECDH_OK = 0
const ECDH_INVALID_PUBLIC_KEY = -2
const ECDH_ERROR = -3
const ECDH_INVALID = -4
const EGS_BLS24 = MODBYTES_480_56
const EFS_BLS24 = MODBYTES_480_56
const EGS_BLS381 = MODBYTES_384_58
const EFS_BLS381 = MODBYTES_384_58
const EGS_BLS383 = MODBYTES_384_58
const EFS_BLS383 = MODBYTES_384_58
const EGS_BLS461 = MODBYTES_464_60
const EFS_BLS461 = MODBYTES_464_60
const EGS_BLS48 = MODBYTES_560_58
const EFS_BLS48 = MODBYTES_560_58
const EGS_BN254 = MODBYTES_256_56
const EFS_BN254 = MODBYTES_256_56
const EGS_BN254CX = MODBYTES_256_56
const EFS_BN254CX = MODBYTES_256_56
const EGS_BRAINPOOL = MODBYTES_256_56
const EFS_BRAINPOOL = MODBYTES_256_56
const EGS_C25519 = MODBYTES_256_56
const EFS_C25519 = MODBYTES_256_56
const EGS_C41417 = MODBYTES_416_60
const EFS_C41417 = MODBYTES_416_60
const EGS_ED25519 = MODBYTES_256_56
const EFS_ED25519 = MODBYTES_256_56
const EGS_FP256BN = MODBYTES_256_56
const EFS_FP256BN = MODBYTES_256_56
const EGS_FP512BN = MODBYTES_512_60
const EFS_FP512BN = MODBYTES_512_60
const EGS_GOLDILOCKS = MODBYTES_448_58
const EFS_GOLDILOCKS = MODBYTES_448_58
const EGS_HIFIVE = MODBYTES_336_60
const EFS_HIFIVE = MODBYTES_336_60
const EGS_NIST256 = MODBYTES_256_56
const EFS_NIST256 = MODBYTES_256_56
const EGS_NIST384 = MODBYTES_384_56
const EFS_NIST384 = MODBYTES_384_56
const EGS_NIST521 = MODBYTES_528_60
const EFS_NIST521 = MODBYTES_528_60
const EGS_NUMS256E = MODBYTES_256_56
const EFS_NUMS256E = MODBYTES_256_56
const EGS_NUMS256W = MODBYTES_256_56
const EFS_NUMS256W = MODBYTES_256_56
const EGS_NUMS384E = MODBYTES_384_56
const EFS_NUMS384E = MODBYTES_384_56
const EGS_NUMS384W = MODBYTES_384_56
const EFS_NUMS384W = MODBYTES_384_56
const EGS_NUMS512E = MODBYTES_512_56
const EFS_NUMS512E = MODBYTES_512_56
const EGS_NUMS512W = MODBYTES_512_56
const EFS_NUMS512W = MODBYTES_512_56
const EGS_SECP256K1 = MODBYTES_256_56
const EFS_SECP256K1 = MODBYTES_256_56

@cstruct FP_BLS381 {
    g::Int64[div(sizeof(BIG_384_58),sizeof(Int64))]
    XES::Int32
}

@cstruct FP2_BLS381 {
    a::FP_BLS381
    b::FP_BLS381
}

@cstruct ECP2_BLS381 {
    x::FP2_BLS381
    y::FP2_BLS381
    z::FP2_BLS381
}

@cstruct FP_BLS383 {
    g::Int64[div(sizeof(BIG_384_58),sizeof(Int64))]
    XES::Int32
}

@cstruct FP2_BLS383 {
    a::FP_BLS383
    b::FP_BLS383
}

@cstruct ECP2_BLS383 {
    x::FP2_BLS383
    y::FP2_BLS383
    z::FP2_BLS383
}

@cstruct FP_BLS461 {
    g::Int64[div(sizeof(BIG_464_60),sizeof(Int64))]
    XES::Int32
}

@cstruct FP2_BLS461 {
    a::FP_BLS461
    b::FP_BLS461
}

@cstruct ECP2_BLS461 {
    x::FP2_BLS461
    y::FP2_BLS461
    z::FP2_BLS461
}

@cstruct FP_BN254 {
    g::Int64[div(sizeof(BIG_256_56),sizeof(Int64))]
    XES::Int32
}

@cstruct FP2_BN254 {
    a::FP_BN254
    b::FP_BN254
}

@cstruct ECP2_BN254 {
    x::FP2_BN254
    y::FP2_BN254
    z::FP2_BN254
}

@cstruct FP_BN254CX {
    g::Int64[div(sizeof(BIG_256_56),sizeof(Int64))]
    XES::Int32
}

@cstruct FP2_BN254CX {
    a::FP_BN254CX
    b::FP_BN254CX
}

@cstruct ECP2_BN254CX {
    x::FP2_BN254CX
    y::FP2_BN254CX
    z::FP2_BN254CX
}

@cstruct FP_FP256BN {
    g::Int64[div(sizeof(BIG_256_56),sizeof(Int64))]
    XES::Int32
}

@cstruct FP2_FP256BN {
    a::FP_FP256BN
    b::FP_FP256BN
}

@cstruct ECP2_FP256BN {
    x::FP2_FP256BN
    y::FP2_FP256BN
    z::FP2_FP256BN
}

@cstruct FP_FP512BN {
    g::Int64[div(sizeof(BIG_512_60),sizeof(Int64))]
    XES::Int32
}

@cstruct FP2_FP512BN {
    a::FP_FP512BN
    b::FP_FP512BN
}

@cstruct ECP2_FP512BN {
    x::FP2_FP512BN
    y::FP2_FP512BN
    z::FP2_FP512BN
}

@cstruct FP_BLS24 {
    g::Int64[div(sizeof(BIG_480_56),sizeof(Int64))]
    XES::Int32
}

@cstruct FP2_BLS24 {
    a::FP_BLS24
    b::FP_BLS24
}

@cstruct FP4_BLS24 {
    a::FP2_BLS24
    b::FP2_BLS24
}

@cstruct ECP4_BLS24 {
    x::FP4_BLS24
    y::FP4_BLS24
    z::FP4_BLS24
}

@cstruct FP_BLS48 {
    g::Int64[div(sizeof(BIG_560_58),sizeof(Int64))]
    XES::Int32
}

@cstruct FP2_BLS48 {
    a::FP_BLS48
    b::FP_BLS48
}

@cstruct FP4_BLS48 {
    a::FP2_BLS48
    b::FP2_BLS48
}

@cstruct FP8_BLS48 {
    a::FP4_BLS48
    b::FP4_BLS48
}

@cstruct ECP8_BLS48 {
    x::FP8_BLS48
    y::FP8_BLS48
    z::FP8_BLS48
}

@cstruct FP_ANSSI {
    g::Int64[div(sizeof(BIG_256_56),sizeof(Int64))]
    XES::Int32
}

@cstruct ECP_ANSSI {
    x::FP_ANSSI
    y::FP_ANSSI
    z::FP_ANSSI
}

@cstruct ECP_BLS24 {
    x::FP_BLS24
    y::FP_BLS24
    z::FP_BLS24
}

@cstruct ECP_BLS381 {
    x::FP_BLS381
    y::FP_BLS381
    z::FP_BLS381
}

@cstruct ECP_BLS383 {
    x::FP_BLS383
    y::FP_BLS383
    z::FP_BLS383
}

@cstruct ECP_BLS461 {
    x::FP_BLS461
    y::FP_BLS461
    z::FP_BLS461
}

@cstruct ECP_BLS48 {
    x::FP_BLS48
    y::FP_BLS48
    z::FP_BLS48
}

@cstruct ECP_BN254 {
    x::FP_BN254
    y::FP_BN254
    z::FP_BN254
}

@cstruct ECP_BN254CX {
    x::FP_BN254CX
    y::FP_BN254CX
    z::FP_BN254CX
}

@cstruct FP_BRAINPOOL {
    g::Int64[div(sizeof(BIG_256_56),sizeof(Int64))]
    XES::Int32
}

@cstruct ECP_BRAINPOOL {
    x::FP_BRAINPOOL
    y::FP_BRAINPOOL
    z::FP_BRAINPOOL
}

@cstruct FP_25519 {
    g::Int64[div(sizeof(BIG_256_56),sizeof(Int64))]
    XES::Int32
}

@cstruct ECP_C25519 {
    x::FP_25519
    z::FP_25519
}

@cstruct FP_C41417 {
    g::Int64[div(sizeof(BIG_416_60),sizeof(Int64))]
    XES::Int32
}

@cstruct ECP_C41417 {
    x::FP_C41417
    y::FP_C41417
    z::FP_C41417
}

@cstruct ECP_ED25519 {
    x::FP_25519
    y::FP_25519
    z::FP_25519
}

@cstruct ECP_FP256BN {
    x::FP_FP256BN
    y::FP_FP256BN
    z::FP_FP256BN
}

@cstruct ECP_FP512BN {
    x::FP_FP512BN
    y::FP_FP512BN
    z::FP_FP512BN
}

@cstruct FP_GOLDILOCKS {
    g::Int64[div(sizeof(BIG_448_58),sizeof(Int64))]
    XES::Int32
}

@cstruct ECP_GOLDILOCKS {
    x::FP_GOLDILOCKS
    y::FP_GOLDILOCKS
    z::FP_GOLDILOCKS
}

@cstruct FP_HIFIVE {
    g::Int64[div(sizeof(BIG_336_60),sizeof(Int64))]
    XES::Int32
}

@cstruct ECP_HIFIVE {
    x::FP_HIFIVE
    y::FP_HIFIVE
    z::FP_HIFIVE
}

@cstruct FP_NIST256 {
    g::Int64[div(sizeof(BIG_256_56),sizeof(Int64))]
    XES::Int32
}

@cstruct ECP_NIST256 {
    x::FP_NIST256
    y::FP_NIST256
    z::FP_NIST256
}

@cstruct FP_NIST384 {
    g::Int64[div(sizeof(BIG_384_56),sizeof(Int64))]
    XES::Int32
}

@cstruct ECP_NIST384 {
    x::FP_NIST384
    y::FP_NIST384
    z::FP_NIST384
}

@cstruct FP_NIST521 {
    g::Int64[div(sizeof(BIG_528_60),sizeof(Int64))]
    XES::Int32
}

@cstruct ECP_NIST521 {
    x::FP_NIST521
    y::FP_NIST521
    z::FP_NIST521
}

@cstruct FP_256PME {
    g::Int64[div(sizeof(BIG_256_56),sizeof(Int64))]
    XES::Int32
}

@cstruct ECP_NUMS256E {
    x::FP_256PME
    y::FP_256PME
    z::FP_256PME
}

@cstruct FP_256PMW {
    g::Int64[div(sizeof(BIG_256_56),sizeof(Int64))]
    XES::Int32
}

@cstruct ECP_NUMS256W {
    x::FP_256PMW
    y::FP_256PMW
    z::FP_256PMW
}

@cstruct FP_384PM {
    g::Int64[div(sizeof(BIG_384_56),sizeof(Int64))]
    XES::Int32
}

@cstruct ECP_NUMS384E {
    x::FP_384PM
    y::FP_384PM
    z::FP_384PM
}

@cstruct ECP_NUMS384W {
    x::FP_384PM
    y::FP_384PM
    z::FP_384PM
}

@cstruct FP_512PM {
    g::Int64[div(sizeof(BIG_512_56),sizeof(Int64))]
    XES::Int32
}

@cstruct ECP_NUMS512E {
    x::FP_512PM
    y::FP_512PM
    z::FP_512PM
}

@cstruct ECP_NUMS512W {
    x::FP_512PM
    y::FP_512PM
    z::FP_512PM
}

@cstruct FP_SECP256K1 {
    g::Int64[div(sizeof(BIG_256_56),sizeof(Int64))]
    XES::Int32
}

@cstruct ECP_SECP256K1 {
    x::FP_SECP256K1
    y::FP_SECP256K1
    z::FP_SECP256K1
}

const HFLEN_2048 = FFLEN_2048 / 2
const P_MBITS_2048 = MODBYTES_1024_58 * 8
const P_TBITS_2048 = P_MBITS_2048 % BASEBITS_1024_58

# Skipping MacroDefinition: P_EXCESS_2048 ( a ) ( ( ( a [ NLEN_1024_58 - 1 ] ) >> ( P_TBITS_2048 ) ) + 1 )
# Skipping MacroDefinition: P_FEXCESS_2048 ( ( chunk ) 1 << ( BASEBITS_1024_58 * NLEN_1024_58 - P_MBITS_2048 - 1 ) )

const HFLEN_3072 = FFLEN_3072 / 2
const P_MBITS_3072 = MODBYTES_384_56 * 8
const P_TBITS_3072 = P_MBITS_3072 % BASEBITS_384_56

# Skipping MacroDefinition: P_EXCESS_3072 ( a ) ( ( ( a [ NLEN_384_56 - 1 ] ) >> ( P_TBITS_3072 ) ) + 1 )
# Skipping MacroDefinition: P_FEXCESS_3072 ( ( chunk ) 1 << ( BASEBITS_384_56 * NLEN_384_56 - P_MBITS_3072 - 1 ) )

const HFLEN_4096 = FFLEN_4096 / 2
const P_MBITS_4096 = MODBYTES_512_60 * 8
const P_TBITS_4096 = P_MBITS_4096 % BASEBITS_512_60

# Skipping MacroDefinition: P_EXCESS_4096 ( a ) ( ( ( a [ NLEN_512_60 - 1 ] ) >> ( P_TBITS_4096 ) ) + 1 )
# Skipping MacroDefinition: P_FEXCESS_4096 ( ( chunk ) 1 << ( BASEBITS_512_60 * NLEN_512_60 - P_MBITS_4096 - 1 ) )

@cstruct FP4_BLS381 {
    a::FP2_BLS381
    b::FP2_BLS381
}

@cstruct FP12_BLS381 {
    a::FP4_BLS381
    b::FP4_BLS381
    c::FP4_BLS381
    type::Cint
}

@cstruct FP4_BLS383 {
    a::FP2_BLS383
    b::FP2_BLS383
}

@cstruct FP12_BLS383 {
    a::FP4_BLS383
    b::FP4_BLS383
    c::FP4_BLS383
    type::Cint
}

@cstruct FP4_BLS461 {
    a::FP2_BLS461
    b::FP2_BLS461
}

@cstruct FP12_BLS461 {
    a::FP4_BLS461
    b::FP4_BLS461
    c::FP4_BLS461
    type::Cint
}

@cstruct FP4_BN254 {
    a::FP2_BN254
    b::FP2_BN254
}

@cstruct FP12_BN254 {
    a::FP4_BN254
    b::FP4_BN254
    c::FP4_BN254
    type::Cint
}

@cstruct FP4_BN254CX {
    a::FP2_BN254CX
    b::FP2_BN254CX
}

@cstruct FP12_BN254CX {
    a::FP4_BN254CX
    b::FP4_BN254CX
    c::FP4_BN254CX
    type::Cint
}

@cstruct FP4_FP256BN {
    a::FP2_FP256BN
    b::FP2_FP256BN
}

@cstruct FP12_FP256BN {
    a::FP4_FP256BN
    b::FP4_FP256BN
    c::FP4_FP256BN
    type::Cint
}

@cstruct FP4_FP512BN {
    a::FP2_FP512BN
    b::FP2_FP512BN
}

@cstruct FP12_FP512BN {
    a::FP4_FP512BN
    b::FP4_FP512BN
    c::FP4_FP512BN
    type::Cint
}

@cstruct FP16_BLS48 {
    a::FP8_BLS48
    b::FP8_BLS48
}

@cstruct FP8_BLS24 {
    a::FP4_BLS24
    b::FP4_BLS24
}

@cstruct FP24_BLS24 {
    a::FP8_BLS24
    b::FP8_BLS24
    c::FP8_BLS24
    type::Cint
}

@cstruct FP48_BLS48 {
    a::FP16_BLS48
    b::FP16_BLS48
    c::FP16_BLS48
    type::Cint
}

const MODBITS_25519 = MBITS_25519
const TBITS_25519 = MBITS_25519 % BASEBITS_256_56

# Skipping MacroDefinition: TMASK_25519 ( ( ( chunk ) 1 << TBITS_25519 ) - 1 )
# Skipping MacroDefinition: FEXCESS_25519 ( ( ( sign32 ) 1 << MAXXES_25519 ) - 1 )
# Skipping MacroDefinition: OMASK_25519 ( - ( ( chunk ) ( 1 ) << TBITS_25519 ) )

const MODBITS_256PME = MBITS_256PME
const TBITS_256PME = MBITS_256PME % BASEBITS_256_56

# Skipping MacroDefinition: TMASK_256PME ( ( ( chunk ) 1 << TBITS_256PME ) - 1 )
# Skipping MacroDefinition: FEXCESS_256PME ( ( ( sign32 ) 1 << MAXXES_256PME ) - 1 )
# Skipping MacroDefinition: OMASK_256PME ( - ( ( chunk ) ( 1 ) << TBITS_256PME ) )

const MODBITS_256PMW = MBITS_256PMW
const TBITS_256PMW = MBITS_256PMW % BASEBITS_256_56

# Skipping MacroDefinition: TMASK_256PMW ( ( ( chunk ) 1 << TBITS_256PMW ) - 1 )
# Skipping MacroDefinition: FEXCESS_256PMW ( ( ( sign32 ) 1 << MAXXES_256PMW ) - 1 )
# Skipping MacroDefinition: OMASK_256PMW ( - ( ( chunk ) ( 1 ) << TBITS_256PMW ) )

const MODBITS_384PM = MBITS_384PM
const TBITS_384PM = MBITS_384PM % BASEBITS_384_56

# Skipping MacroDefinition: TMASK_384PM ( ( ( chunk ) 1 << TBITS_384PM ) - 1 )
# Skipping MacroDefinition: FEXCESS_384PM ( ( ( sign32 ) 1 << MAXXES_384PM ) - 1 )
# Skipping MacroDefinition: OMASK_384PM ( - ( ( chunk ) ( 1 ) << TBITS_384PM ) )

const MODBITS_512PM = MBITS_512PM
const TBITS_512PM = MBITS_512PM % BASEBITS_512_56

# Skipping MacroDefinition: TMASK_512PM ( ( ( chunk ) 1 << TBITS_512PM ) - 1 )
# Skipping MacroDefinition: FEXCESS_512PM ( ( ( sign32 ) 1 << MAXXES_512PM ) - 1 )
# Skipping MacroDefinition: OMASK_512PM ( - ( ( chunk ) ( 1 ) << TBITS_512PM ) )

const MODBITS_ANSSI = MBITS_ANSSI
const TBITS_ANSSI = MBITS_ANSSI % BASEBITS_256_56

# Skipping MacroDefinition: TMASK_ANSSI ( ( ( chunk ) 1 << TBITS_ANSSI ) - 1 )
# Skipping MacroDefinition: FEXCESS_ANSSI ( ( ( sign32 ) 1 << MAXXES_ANSSI ) - 1 )
# Skipping MacroDefinition: OMASK_ANSSI ( - ( ( chunk ) ( 1 ) << TBITS_ANSSI ) )

const MODBITS_BLS24 = MBITS_BLS24
const TBITS_BLS24 = MBITS_BLS24 % BASEBITS_480_56

# Skipping MacroDefinition: TMASK_BLS24 ( ( ( chunk ) 1 << TBITS_BLS24 ) - 1 )
# Skipping MacroDefinition: FEXCESS_BLS24 ( ( ( sign32 ) 1 << MAXXES_BLS24 ) - 1 )
# Skipping MacroDefinition: OMASK_BLS24 ( - ( ( chunk ) ( 1 ) << TBITS_BLS24 ) )

const MODBITS_BLS381 = MBITS_BLS381
const TBITS_BLS381 = MBITS_BLS381 % BASEBITS_384_58

# Skipping MacroDefinition: TMASK_BLS381 ( ( ( chunk ) 1 << TBITS_BLS381 ) - 1 )
# Skipping MacroDefinition: FEXCESS_BLS381 ( ( ( sign32 ) 1 << MAXXES_BLS381 ) - 1 )
# Skipping MacroDefinition: OMASK_BLS381 ( - ( ( chunk ) ( 1 ) << TBITS_BLS381 ) )

const MODBITS_BLS383 = MBITS_BLS383
const TBITS_BLS383 = MBITS_BLS383 % BASEBITS_384_58

# Skipping MacroDefinition: TMASK_BLS383 ( ( ( chunk ) 1 << TBITS_BLS383 ) - 1 )
# Skipping MacroDefinition: FEXCESS_BLS383 ( ( ( sign32 ) 1 << MAXXES_BLS383 ) - 1 )
# Skipping MacroDefinition: OMASK_BLS383 ( - ( ( chunk ) ( 1 ) << TBITS_BLS383 ) )

const MODBITS_BLS461 = MBITS_BLS461
const TBITS_BLS461 = MBITS_BLS461 % BASEBITS_464_60

# Skipping MacroDefinition: TMASK_BLS461 ( ( ( chunk ) 1 << TBITS_BLS461 ) - 1 )
# Skipping MacroDefinition: FEXCESS_BLS461 ( ( ( sign32 ) 1 << MAXXES_BLS461 ) - 1 )
# Skipping MacroDefinition: OMASK_BLS461 ( - ( ( chunk ) ( 1 ) << TBITS_BLS461 ) )

const MODBITS_BLS48 = MBITS_BLS48
const TBITS_BLS48 = MBITS_BLS48 % BASEBITS_560_58

# Skipping MacroDefinition: TMASK_BLS48 ( ( ( chunk ) 1 << TBITS_BLS48 ) - 1 )
# Skipping MacroDefinition: FEXCESS_BLS48 ( ( ( sign32 ) 1 << MAXXES_BLS48 ) - 1 )
# Skipping MacroDefinition: OMASK_BLS48 ( - ( ( chunk ) ( 1 ) << TBITS_BLS48 ) )

const MODBITS_BN254 = MBITS_BN254
const TBITS_BN254 = MBITS_BN254 % BASEBITS_256_56

# Skipping MacroDefinition: TMASK_BN254 ( ( ( chunk ) 1 << TBITS_BN254 ) - 1 )
# Skipping MacroDefinition: FEXCESS_BN254 ( ( ( sign32 ) 1 << MAXXES_BN254 ) - 1 )
# Skipping MacroDefinition: OMASK_BN254 ( - ( ( chunk ) ( 1 ) << TBITS_BN254 ) )

const MODBITS_BN254CX = MBITS_BN254CX
const TBITS_BN254CX = MBITS_BN254CX % BASEBITS_256_56

# Skipping MacroDefinition: TMASK_BN254CX ( ( ( chunk ) 1 << TBITS_BN254CX ) - 1 )
# Skipping MacroDefinition: FEXCESS_BN254CX ( ( ( sign32 ) 1 << MAXXES_BN254CX ) - 1 )
# Skipping MacroDefinition: OMASK_BN254CX ( - ( ( chunk ) ( 1 ) << TBITS_BN254CX ) )

const MODBITS_BRAINPOOL = MBITS_BRAINPOOL
const TBITS_BRAINPOOL = MBITS_BRAINPOOL % BASEBITS_256_56

# Skipping MacroDefinition: TMASK_BRAINPOOL ( ( ( chunk ) 1 << TBITS_BRAINPOOL ) - 1 )
# Skipping MacroDefinition: FEXCESS_BRAINPOOL ( ( ( sign32 ) 1 << MAXXES_BRAINPOOL ) - 1 )
# Skipping MacroDefinition: OMASK_BRAINPOOL ( - ( ( chunk ) ( 1 ) << TBITS_BRAINPOOL ) )

const MODBITS_C41417 = MBITS_C41417
const TBITS_C41417 = MBITS_C41417 % BASEBITS_416_60

# Skipping MacroDefinition: TMASK_C41417 ( ( ( chunk ) 1 << TBITS_C41417 ) - 1 )
# Skipping MacroDefinition: FEXCESS_C41417 ( ( ( sign32 ) 1 << MAXXES_C41417 ) - 1 )
# Skipping MacroDefinition: OMASK_C41417 ( - ( ( chunk ) ( 1 ) << TBITS_C41417 ) )

const MODBITS_FP256BN = MBITS_FP256BN
const TBITS_FP256BN = MBITS_FP256BN % BASEBITS_256_56

# Skipping MacroDefinition: TMASK_FP256BN ( ( ( chunk ) 1 << TBITS_FP256BN ) - 1 )
# Skipping MacroDefinition: FEXCESS_FP256BN ( ( ( sign32 ) 1 << MAXXES_FP256BN ) - 1 )
# Skipping MacroDefinition: OMASK_FP256BN ( - ( ( chunk ) ( 1 ) << TBITS_FP256BN ) )

const MODBITS_FP512BN = MBITS_FP512BN
const TBITS_FP512BN = MBITS_FP512BN % BASEBITS_512_60

# Skipping MacroDefinition: TMASK_FP512BN ( ( ( chunk ) 1 << TBITS_FP512BN ) - 1 )
# Skipping MacroDefinition: FEXCESS_FP512BN ( ( ( sign32 ) 1 << MAXXES_FP512BN ) - 1 )
# Skipping MacroDefinition: OMASK_FP512BN ( - ( ( chunk ) ( 1 ) << TBITS_FP512BN ) )

const MODBITS_GOLDILOCKS = MBITS_GOLDILOCKS
const TBITS_GOLDILOCKS = MBITS_GOLDILOCKS % BASEBITS_448_58

# Skipping MacroDefinition: TMASK_GOLDILOCKS ( ( ( chunk ) 1 << TBITS_GOLDILOCKS ) - 1 )
# Skipping MacroDefinition: FEXCESS_GOLDILOCKS ( ( ( sign32 ) 1 << MAXXES_GOLDILOCKS ) - 1 )
# Skipping MacroDefinition: OMASK_GOLDILOCKS ( - ( ( chunk ) ( 1 ) << TBITS_GOLDILOCKS ) )

const MODBITS_HIFIVE = MBITS_HIFIVE
const TBITS_HIFIVE = MBITS_HIFIVE % BASEBITS_336_60

# Skipping MacroDefinition: TMASK_HIFIVE ( ( ( chunk ) 1 << TBITS_HIFIVE ) - 1 )
# Skipping MacroDefinition: FEXCESS_HIFIVE ( ( ( sign32 ) 1 << MAXXES_HIFIVE ) - 1 )
# Skipping MacroDefinition: OMASK_HIFIVE ( - ( ( chunk ) ( 1 ) << TBITS_HIFIVE ) )

const MODBITS_NIST256 = MBITS_NIST256
const TBITS_NIST256 = MBITS_NIST256 % BASEBITS_256_56

# Skipping MacroDefinition: TMASK_NIST256 ( ( ( chunk ) 1 << TBITS_NIST256 ) - 1 )
# Skipping MacroDefinition: FEXCESS_NIST256 ( ( ( sign32 ) 1 << MAXXES_NIST256 ) - 1 )
# Skipping MacroDefinition: OMASK_NIST256 ( - ( ( chunk ) ( 1 ) << TBITS_NIST256 ) )

const MODBITS_NIST384 = MBITS_NIST384
const TBITS_NIST384 = MBITS_NIST384 % BASEBITS_384_56

# Skipping MacroDefinition: TMASK_NIST384 ( ( ( chunk ) 1 << TBITS_NIST384 ) - 1 )
# Skipping MacroDefinition: FEXCESS_NIST384 ( ( ( sign32 ) 1 << MAXXES_NIST384 ) - 1 )
# Skipping MacroDefinition: OMASK_NIST384 ( - ( ( chunk ) ( 1 ) << TBITS_NIST384 ) )

const MODBITS_NIST521 = MBITS_NIST521
const TBITS_NIST521 = MBITS_NIST521 % BASEBITS_528_60

# Skipping MacroDefinition: TMASK_NIST521 ( ( ( chunk ) 1 << TBITS_NIST521 ) - 1 )
# Skipping MacroDefinition: FEXCESS_NIST521 ( ( ( sign32 ) 1 << MAXXES_NIST521 ) - 1 )
# Skipping MacroDefinition: OMASK_NIST521 ( - ( ( chunk ) ( 1 ) << TBITS_NIST521 ) )

const MODBITS_SECP256K1 = MBITS_SECP256K1
const TBITS_SECP256K1 = MBITS_SECP256K1 % BASEBITS_256_56

# Skipping MacroDefinition: TMASK_SECP256K1 ( ( ( chunk ) 1 << TBITS_SECP256K1 ) - 1 )
# Skipping MacroDefinition: FEXCESS_SECP256K1 ( ( ( sign32 ) 1 << MAXXES_SECP256K1 ) - 1 )
# Skipping MacroDefinition: OMASK_SECP256K1 ( - ( ( chunk ) ( 1 ) << TBITS_SECP256K1 ) )

const PGS_BLS24 = MODBYTES_480_56
const PFS_BLS24 = MODBYTES_480_56
const MPIN_OK = 0
const MPIN_INVALID_POINT = -14
const MPIN_BAD_PIN = -19
const MAXPIN = 10000
const PBLEN = 14
const MESSAGE_SIZE = 256
const M_SIZE_BLS24 = MESSAGE_SIZE + 2PFS_BLS24 + 1
const PGS_BLS48 = MODBYTES_560_58
const PFS_BLS48 = MODBYTES_560_58
const M_SIZE_BLS48 = MESSAGE_SIZE + 2PFS_BLS48 + 1
const PGS_BLS381 = MODBYTES_384_58
const PFS_BLS381 = MODBYTES_384_58
const MPIN_PAS = 16
const M_SIZE_BLS381 = MESSAGE_SIZE + 2PFS_BLS381 + 1
const PGS_BLS383 = MODBYTES_384_58
const PFS_BLS383 = MODBYTES_384_58
const M_SIZE_BLS383 = MESSAGE_SIZE + 2PFS_BLS383 + 1
const PGS_BLS461 = MODBYTES_464_60
const PFS_BLS461 = MODBYTES_464_60
const M_SIZE_BLS461 = MESSAGE_SIZE + 2PFS_BLS461 + 1
const PGS_BN254 = MODBYTES_256_56
const PFS_BN254 = MODBYTES_256_56
const M_SIZE_BN254 = MESSAGE_SIZE + 2PFS_BN254 + 1
const PGS_BN254CX = MODBYTES_256_56
const PFS_BN254CX = MODBYTES_256_56
const M_SIZE_BN254CX = MESSAGE_SIZE + 2PFS_BN254CX + 1
const PGS_FP256BN = MODBYTES_256_56
const PFS_FP256BN = MODBYTES_256_56
const M_SIZE_FP256BN = MESSAGE_SIZE + 2PFS_FP256BN + 1
const PGS_FP512BN = MODBYTES_512_60
const PFS_FP512BN = MODBYTES_512_60
const M_SIZE_FP512BN = MESSAGE_SIZE + 2PFS_FP512BN + 1
const TIME_SLOT_MINUTES = 1440
const HASH_TYPE_RSA_2048 = SHA256
const RFS_2048 = MODBYTES_1024_58 * FFLEN_2048

@cstruct rsa_public_key_2048 {
    e::Int32
    n::BIG_1024_58[2]
}

@cstruct rsa_private_key_2048 {
    p::BIG_1024_58[1]
    q::BIG_1024_58[1]
    dp::BIG_1024_58[1]
    dq::BIG_1024_58[1]
    c::BIG_1024_58[1]
}

const HASH_TYPE_RSA_3072 = SHA256
const RFS_3072 = MODBYTES_384_56 * FFLEN_3072

@cstruct rsa_public_key_3072 {
    e::Int32
    n::BIG_384_56[8]
}

@cstruct rsa_private_key_3072 {
    p::BIG_384_56[4]
    q::BIG_384_56[4]
    dp::BIG_384_56[4]
    dq::BIG_384_56[4]
    c::BIG_384_56[4]
}

const HASH_TYPE_RSA_4096 = SHA256
const RFS_4096 = MODBYTES_512_60 * FFLEN_4096

@cstruct rsa_public_key_4096 {
    e::Int32
    n::BIG_512_60[8]
}

@cstruct rsa_private_key_4096 {
    p::BIG_512_60[4]
    q::BIG_512_60[4]
    dp::BIG_512_60[4]
    dq::BIG_512_60[4]
    c::BIG_512_60[4]
}

const MAX_RSA_BYTES = 512
const WCC_PGS_BLS24 = MODBYTES_480_56
const WCC_PFS_BLS24 = MODBYTES_480_56
const WCC_OK = 0
const WCC_INVALID_POINT = -51
const PIV = 12
const PTAG = 16
const WCC_PGS_BLS48 = MODBYTES_560_58
const WCC_PFS_BLS48 = MODBYTES_560_58
const WCC_PGS_BLS381 = MODBYTES_384_58
const WCC_PFS_BLS381 = MODBYTES_384_58
const WCC_PGS_BLS383 = MODBYTES_384_58
const WCC_PFS_BLS383 = MODBYTES_384_58
const WCC_PGS_BLS461 = MODBYTES_464_60
const WCC_PFS_BLS461 = MODBYTES_464_60
const WCC_PGS_BN254 = MODBYTES_256_56
const WCC_PFS_BN254 = MODBYTES_256_56
const WCC_PGS_BN254CX = MODBYTES_256_56
const WCC_PFS_BN254CX = MODBYTES_256_56
const WCC_PGS_FP256BN = MODBYTES_256_56
const WCC_PFS_FP256BN = MODBYTES_256_56
const WCC_PGS_FP512BN = MODBYTES_512_60
const WCC_PFS_FP512BN = MODBYTES_512_60

@cstruct pktype {
    type::Cint
    hash::Cint
    curve::Cint
}


module BoundingVolumeTrees

using StaticArrays
import StaticArrays.insert
const Point{T} = SVector{3,T}
const Vector3 = Point

# some WIP tree structures intended to replace the older DynamicAABBTree
include("BoundingVolumeTrees/BinaryTrees.jl")
include("BoundingVolumeTrees/IndexedBinaryTrees.jl")
include("BoundingVolumeTrees/BalancedTrees.jl")
include("BoundingVolumeTrees/AABBs.jl")
include("BoundingVolumeTrees/AABBTrees.jl")

#!me ideally this isn't needed.  attempt to have a pool of leaves at indices 1:nleaves reserved
function index_entities(aabbtree::B) where {T,S,B<:Union{IndexedBinaryTree{AVLData{AABBNodeData{T,S}}},
                                                         IndexedBinaryTree{AABBNodeData{T,S}}}}
  [x[2] for x in sort(map(n->(entity(value(data(n))),index(n)),Leaves(bt)), by=first)]
end

#!me this one is optimized, but lacking features like balancing
#!me also the design is a bit grim
# however, it works, so it's the one we use in HalfEdges for now
include("BoundingVolumeTrees/DynamicAABBTrees.jl")

end


module EnergySystemModeling

include("model.jl")
include("io.jl")
export EnergySystemModel,
    Specs,
    Params,
    JuMPObj,
    JuMPVar,
    retrieve_data,
    Expressions,
    equivalent_annual_cost,
    save_json,
    load_json,
    create_nodedata,
    replace_nans,
    getdispatch,
    read_clusters,
    unpack_clusters_features

include("plotting.jl")
export plot_objective_values,
    plot_generation_dispatch,
    plot_generation_capacities,
    plot_generation_capacities_stacked,
    plot_transmission_flow,
    plot_transmission_capacities,
    plot_transmission_bars,
    plot_storage_level,
    plot_storage_capacities,
    plot_loss_of_load,
    plot_box,
    plot_box_all,
    plot_dispatch_bars

include("aggreg.jl")
export SeriesInstance,
    ClustInstance,
    AggregInstance,
    DistUpdate,
    load_series_instance,
    load_clust_instance,
    aggreg1D,
    cdad,
    search_min_dist,
    compute_dist,
    update_marker,
    replace_lines,
    update_clust!,
    update_k!,
    find_clusters!
end # module


module Utilities
using BoundaryIntegralMethod
using BoundaryIntegralMethod.Husimi
using JLD

export writeWithHusimi

function writeWithHusimi(filename, b, qs, ps, hVals, kf, xf, field)
    boundaryField = [boundaryPoint(b, t) for t in 0.:pi/length(xf):2pi][1:end-1]
    ts = (0:2*pi/length(qs):2pi)[1:end-1]
    jldopen(filename, "w") do file
        write(file, "qs", qs)
        write(file, "ps", convert(Array, ps))
        write(file, "hVals", hVals)
        write(file, "ts", convert(Array,ts))
        write(file, "kf/re", real(kf))
        write(file, "kf/im", imag(kf))
        write(file, "kf/full", kf)
        write(file, "xf/re", real.(xf))
        write(file, "xf/im", imag.(xf))
        write(file, "xf/full", xf)
        write(file, "field/re", real.(field))
        write(file, "field/im", imag.(field))
        write(file, "field/full", field)
        write(file, "boundaryPoints/full", boundaryField)
        write(file, "boundaryPoints/x", [p.x for p in boundaryField])
        write(file, "boundaryPoints/y", [p.y for p in boundaryField])
        write(file, "boundary", b)
    end
end
end


include( "uses.jl")

const FORM_EXTRA =Dict(
	"border-bottom"=>"1px dashed #aaaaaa",
	"margin-bottom"=>"5px",
	"margin-top"=>"5px")

const PREAMBLE = """
Regardless of politics, I imagine most people an agree on the outlines of what
our taxation and benefit system should do. Give some support to people who haven't got much,
(depending who you ask, maybe take that support away from better off people); as you
get better off, pay a bit (or a lot) more in tax. But that's not always what the systems
we have do. Here's a simulation showing how the rules of the tax and benefit system
interact to make it almost impossible for households to make sensible choices about
whether it's worth taking a job, working a bit more, or going for a promotion.  Using the
controls to the left, you can see from the charts how the choices between what you earn and what
you end up with vary for different family times (renting or owning, with and without children, high or low earners).
"""

const INFO = """
The graph shows how net the net income of a household (i.e. after deducting taxes and optionally housing costs, then adding benefits)
varies with gross earnings, under the new [Universal Credit](https://www.gov.uk/universal-credit)
benefit system and the
['legacy' Tax Credit system](https://www.gov.uk/topic/benefits-credits/tax-credits) which UC is gradually replacing. You can also switch to an 'economist's
view' of the labour/leisure choice. Move your mouse over the chart to see a detailed
breakdown of what's happening at each point.

For simplicity, we assume:

* only one person in the household works;
* the adults are of working age;
* there are no other sources of income, for example savings income or disability benefits; and
* the working person faces a fixed hourly wage and can work any hours at that wage.

You'll see that the graphs are complicated enough even with those simplifications.
This is especially so for families with large housing costs
or several children, for whom the [Benefit Cap](https://www.gov.uk/benefit-cap) bites.

See [here](https://stb.virtual-worlds.scot/bc-intro.html) for more on the ideas behind budget constraints.

* Created with [Julia](https://julialang.org/) | [Dash](https://dash-julia.plotly.com/) | [Plotly](https://plotly.com/julia/) | [Budget Constraint Generator](https://github.com/grahamstark/BudgetConstraints.jl);
* Part of the [Scottish Tax Benefit Model](https://github.com/grahamstark/ScottishTaxBenefitModel.jl);
* Open Source software released under the [MIT Licence](https://github.com/grahamstark/Visualisations.jl/blob/main/LICENSE). [Source Code](https://github.com/grahamstark/Visualisations.jl).
"""

"""
Generate a pair of budget constraints (as Dataframes) for the given household.
"""
function getbc(
	hh  :: Household,
	sys :: TaxBenefitSystem,
	wage :: Real,
	settings :: Settings )::Tuple
	defroute = settings.means_tested_routing

	settings.means_tested_routing = lmt_full
	lbc = BCCalcs.makebc( hh, sys, settings, wage )

	settings.means_tested_routing = uc_full
	ubc = BCCalcs.makebc( hh, sys, settings, wage )

	settings.means_tested_routing = defroute
    (lbc,ubc)
end

"""
Retrieve one of the model's example households & overwrite a few fields
to make things simpler.
"""
function get_hh(
	tenure    :: AbstractString,
	bedrooms  :: Integer,
	hcost     :: Real,
	marrstat    :: AbstractString,
	chu5      :: Integer,
	ch5p      :: Integer ) :: Household
	hh = get_example( single_hh )
	head = get_head(hh)
	head.age = 30
	sp = get_spouse(hh)
	enable!(head) # clear dla stuff from example
	hh.tenure = if tenure == "private"
		Private_Rented_Unfurnished
	elseif tenure == "council"
		Council_Rented
	elseif tenure == "owner"
		Mortgaged_Or_Shared
	else
		@assert false "$tenure not recognised"
	end
	hh.bedrooms = bedrooms
	hh.other_housing_charges = hh.water_and_sewerage = 0
	if hh.tenure == Mortgaged_Or_Shared
		hh.mortgage_payment = hcost
		hh.mortgage_interest = hcost
		hh.gross_rent = 0
	else
		hh.mortgage_payment = 0
		hh.mortgage_interest = 0
		hh.gross_rent = hcost
	end
	if marrstat == "couple"
		sex = head.sex == Male ? Female : Male # hetero ..
		add_spouse!( hh, 30, sex )
		sp = get_spouse(hh)
		enable!(sp)
		set_wage!( sp, 0, 10 )
	end
	age = 0
	for ch in 1:chu5
		sex = ch % 1 == 0 ? Male : Female
		age += 1
		add_child!( hh, age, sex )
	end
	age = 7
	for ch in 1:ch5p
		sex = ch % 1 == 0 ? Male : Female
		age += 1
		add_child!( hh, age, sex )
	end
	set_wage!( head, 0, 10 )
	for (pid,pers) in hh.people
		# println( "age=$(pers.age) empstat=$(pers.employment_status) " )
		empty!( pers.income )
		empty!( pers.assets )
	end
	return hh
end

const MAX_HRS = 80

function gross_to_leisure!( bc :: DataFrame, wage::Real)
	gross = bc[:,:gross]
	l = MAX_HRS .- (gross ./ wage)
	w = MAX_HRS .- l
	# println(l)
	bc.leisure = l
	bc.ls = w
	# some nice way
	p = 0
	for r in eachrow(bc)
		p += 1
		if r.leisure < 0
			break
		end
	end
	bc = bc[1:p,:]
end

"""
Plot two budget constraints (contained in dataframes) - legacy & universal credit.
"""
function econ_bcplot( lbc:: DataFrame, ubc :: DataFrame, wage :: Real, ytitle :: String )
	# legacy
	gross_to_leisure!(lbc, wage )
	bl = scatter(
           lbc,
		   x=:leisure,
		   y=:net,
           mode="line",
		   name="Legacy Benefits",
		   text=:simplelabel,
		   hoverinfo="text"
       )
	# uc
	gross_to_leisure!(ubc, wage )
	bu = scatter(
		ubc,
		x=:leisure,
		y=:net,
		mode="line",
		name="Universal Credit",
		text=:simplelabel,
		hoverinfo="text"
		# ,xaxis="x2"
	)
	layout = Layout(
		title="Budget Constraint: Legacy Benefits vs Universal Credit",
        xaxis_title="Leisure (hours p.w.)",
        yaxis_title=ytitle,
		xaxis_range=[0, MAX_HRS ],
		yaxis_range=[0, 1_500],
		#=
		xaxis2=attr(

            title="Hours of work",
			# titlefont_color="blue",
            overlaying="x",
			side="bottom",
			position=0.15,
			anchor="free"

        ),
		=#

		legend=attr(x=0.01, y=0.95),
		width=700,
		height=700)
	p = PlotlyJS.Plot( [bl, bu], layout)
	# (typeof(p))
	return p
end

"""
Plot two budget constraints (contained in dataframes) - legacy & universal credit.
"""
function bcplot( lbc:: DataFrame, ubc :: DataFrame, ytitle :: String )
	# legacy
	bl = scatter(
           lbc,
		   x=:gross,
		   y=:net,
           mode="line",
		   name="Legacy Benefits",
		   text=:simplelabel,
		   hoverinfo="text"
       )
	# uc
	bu = scatter(
		ubc,
		x=:gross,
		y=:net,
		mode="line",
		name="Universal Credit",
		text=:simplelabel,
		hoverinfo="text"
	)
	# 45% line
	gn = scatter(y=[0,1500], x=[0,1500], showlegend=false, name="")

	gn["marker"] = Dict(:color => "#ccc",
                        :line => Dict(:color=> "#ccc",
                        :width=> 0.5))
	layout = Layout(
		title="Budget Constraint: Legacy Benefits vs Universal Credit",
        xaxis_title="Household Gross Earnings £p.w.",
        yaxis_title=ytitle,
		xaxis_range=[0, 1_200],
		yaxis_range=[0, 1_200],
		legend=attr(x=0.01, y=0.95),
		width=700,
		height=700)
	p = PlotlyJS.Plot( [gn, bl, bu], layout)
	# (typeof(p))
	return p
end

"""
Create the whole plot for the named household.
"""
function doplot(
	wage :: Real,
	tenure :: AbstractString,
	bedrooms::Integer,
	hcost::Real,
	marrstat::AbstractString,
	chu5::Integer,
	ch5p::Integer,
	view :: AbstractString,
	target_str :: AbstractString )
	hh = get_hh( tenure, bedrooms, hcost, marrstat, chu5, ch5p )
	# println(to_md_table(hh))
	settings = Settings()
	ytitle = ""
	target = nothing
	if target_str == "ahc_hh"
		target = ahc_hh
		ytitle = "Household Net Income After Housing Costs £p.w."
	elseif target_str == "bhc_hh"
		target = bhc_hh
		ytitle = "Household Net Income Before Housing Costs £p.w."
	elseif target_str == "total_bens"
		ytitle = "Total State Benefits received £pw"
		target = total_bens
	elseif target_str == "total_taxes"
		ytitle = "Total Income Tax and NI Payments £pw"
		target = total_taxes
	end # forgot how to cover
	settings.target_bc_income = target
	lbc, ubc = getbc( hh, sys, wage, settings )
	if view == "l_vs_l"
		figure=econ_bcplot( lbc, ubc, wage, ytitle )
	else
		figure = bcplot( lbc, ubc, ytitle )
	end
	return figure
end

"""
Create the block of sliders and radios on the LHS
"""
function get_input_block()
	return dbc_form(

		[
		dbc_row([
			dbc_col(
				dbc_label("Wage (£ per hour)"; html_for="wage"), width=3
			),
			dbc_col(
				dcc_slider(
					id = "wage",
					min = 1,
					max = 40,
					marks = Dict([Symbol("$v") => Symbol("$v") for v in 0:10:100]),
					value = 10.0,
					step = 1
				)) #
		], style=FORM_EXTRA),
		dbc_row([
			dbc_col(
				dbc_label("Tenure:"; html_for="tenure"), width=3
			),
			dbc_col(
				dbc_radioitems(
					id = "tenure",
					options = [(value = "owner", label= "Owner Occupier"),
					           (value = "private", label="Private Rented"),
							   (value = "council", label="Council/Housing Association")],
					value = "private"
				)
			)
		], style=FORM_EXTRA),
		dbc_row([
			dbc_col(
				dbc_label("Bedrooms:"; html_for="bedrooms"), width=3
			),
			dbc_col(
				dcc_slider(
					id = "bedrooms",
					min = 1,
					max = 5,
					marks = Dict([Symbol("$v") => Symbol("$v") for v in 1:5]),
					value = 2,
					step = 1
    			)
			)
		], style=FORM_EXTRA), # row
		dbc_row([
			dbc_col(
				dbc_label("Housing Costs £pw:"; html_for="hcost"), width=3
			),
			dbc_col(
				dcc_slider(
					id = "hcost",
					min = 0,
					max = 300,
					marks = Dict([Symbol("$v") => Symbol("$v") for v in 0:100:300]),
					value = 100,
					step = 1
    			)
			)
		], style=FORM_EXTRA), # row
		dbc_row([
			dbc_col(
				dbc_label("Adults:"; html_for="marrstat"), width=3
			),
			dbc_col(
				dbc_radioitems(
					id = "marrstat",
					options = [(value = "single", label= "Single"),
							   (value="couple", label="Couple")],
					value = "single"
				)
			)
		], style=FORM_EXTRA), # row
		dbc_row([
			dbc_col(
				dbc_label("Children aged under 5:"; html_for="chu5"), width=3
			),
			dbc_col(
				dcc_slider(
					id = "chu5",
					min = 0,
					max = 5,
					marks = Dict([Symbol("$v") => Symbol("$v") for v in 0:5]),
					value = 0,
					step = 1
    			)
			)
		], style=FORM_EXTRA),
		dbc_row([
			dbc_col(
				dbc_label("Children aged 5+:"; html_for="ch5p"), width=3
			),
			dbc_col(
				dcc_slider(
					id = "ch5p",
					min = 0,
					max = 8,
					marks = Dict([Symbol("$v") => Symbol("$v") for v in 0:8]),
					value = 0,
					step = 1
    			)
			)
		], style=FORM_EXTRA), # row
		dbc_row([
			dbc_col(
				dbc_label("Graph type:"; html_for="view"), width=3
			),
			dbc_col(
				dbc_radioitems(
					id = "view",
					options = [(value = "g_vs_n", label= "Gross vs Net Income"),
					           (value = "l_vs_l", label="Labour/Leisure")],
					value = "g_vs_n")
			)
		], style=FORM_EXTRA), # row
		dbc_row([
			dbc_col(
				dbc_label("Income Measure:"; html_for="target"), width=3
			),
			dbc_col(
				dbc_radioitems(
					id = "target",
					options = [(value = "ahc_hh", label= "After Housing Costs"),
					           (value = "bhc_hh", label= "Before Housing Costs"),
							   (value = "total_bens", label="Total Benefits Received"),
							   (value = "total_taxes", label="Total Taxes Paid") ],
					value = "ahc_hh")
			)
		], style=FORM_EXTRA)
	])
end

"""
an experimental html table. Not actually used.
"""
function generate_table(df :: DataFrame)
	rows = []
	for r in eachrow(df)
		push!(rows,
			html_tr([
				html_td(r.gross),
				html_td(r.net),
				html_td(dcc_markdown(r.label)),
				html_td(dcc_markdown(r.label_p1))]
			)
		)
	end
    t = html_table([
        html_thead(html_tr([html_th("Gross"), html_th("Net"), html_th("Breakdown"), html_th("Breakdown+1p")])),
        html_tbody(rows)])
end

sys = load_file( joinpath( Definitions.MODEL_PARAMS_DIR, "sys_2021_22.jl" ))
load_file!( sys, joinpath( Definitions.MODEL_PARAMS_DIR, "sys_2021-uplift-removed.jl"))
weeklyise!( sys )

app = dash(external_stylesheets=[dbc_themes.UNITED],
	url_base_pathname="/bcd/")
# BOOTSTRAP|SIMPLEX|MINTY|COSMO|SANDSTONE|UNITED|SLATE|SOLAR|UNITED|
app.layout = dbc_container(fluid=true, className="p-5") do
	html_title( "Scotland's Kinkiest Families: Household Budget Constraints")
	html_h1("Scotland's Kinkiest Families: Household Budget Constraints"),
	dbc_row([
		dbc_col( dcc_markdown( PREAMBLE ), width=10)
	]),
	dbc_row([
    	dbc_col(get_input_block(), width=4),
	    dbc_col( dcc_graph( id = "bc-1" ))
		]
	),
	dbc_row([
		dbc_col( dcc_markdown( INFO ), width=10)
	])
end

callback!(
    app,
    Output("bc-1", "figure"),
	# Input( "famchoice", "value"),
	Input( "wage", "value"),
	Input( "tenure", "value"),
	Input( "bedrooms", "value"),
	Input( "hcost", "value" ),
	Input( "marrstat", "value"),
	Input( "chu5", "value"),
	Input( "ch5p", "value"),
	Input( "view", "value"),
	Input( "target", "value")) do wage, tenure, bedrooms, hcost, marrstat, chu5, ch5p, view, target
		return doplot( wage, tenure, bedrooms, hcost, marrstat, chu5, ch5p, view, target )
	end

run_server(app, "0.0.0.0", debug=true )


module Results
    #
    # This module models a complete set of calculation results for all the members of a single household (tax due, benefits entitled to, marginal rate, etc.)
    # It also contains code to map from the Dictionary to Array representation of incomes.
    #
    # FIXME this badly needs rewritten to mirror the structure of the Household in ModelHousehold.jl
    #
    using Parameters: @with_kw
    using DataFrames
    using StaticArrays

    using ScottishTaxBenefitModel

    using .Definitions

    using .GeneralTaxComponents: RateBands

    using .ModelHousehold:
        BenefitUnit,
        BenefitUnits,
        Household,
        Person,
        Pid_Array,
        get_benefit_units

    using .STBIncomes
    using .RunSettings
    using .Utils:
        to_md_table

    export
        BenefitUnitResult,
        HouseholdResult,
        HousingResult,
        IndividualResult,
        ITResult,
        LMTCanApplyFor,
        LMTIncomes,
        LMTResults,
        LocalTaxes,
        NIResult,

        aggregate_tax,
        aggregate!,
        get_indiv_result,
        get_net_income,
        gross_pension_contributions,
        has_any,
        init_benefit_unit_result,
        init_household_result,
        map_incomes,
        to_string,
        total,
        tozero!

    @with_kw mutable struct BenefitCapResults{RT<:Real}
        cap :: RT = zero(RT)
        cap_benefits :: RT = zero(RT)
        reduction :: RT = zero(RT)
    end

    @with_kw mutable struct UCResults{RT<:Real}
        basic_conditions_satisfied :: Bool = false
        disqualified_on_capital :: Bool = false
        work_allowance :: RT = zero(RT)
        earnings_before_allowances :: RT = zero(RT)
        earned_income :: RT = zero(RT)
        other_income :: RT = zero(RT)
        tariff_income :: RT = zero(RT)
        standard_allowance  :: RT = zero(RT)
        child_element ::  RT = zero(RT)
        limited_capacity_for_work_activity_element ::  RT = zero(RT)
        carer_element ::  RT = zero(RT)
        childcare_costs :: RT = zero(RT)
        housing_element :: RT = zero(RT)
        other_housing_costs :: RT = zero(RT)
        assets :: RT = zero(RT)
        maximum  :: RT = zero(RT)
        total_income :: RT = zero(RT)
        transitional_protection :: RT = zero(RT)
        ctr :: RT = zero(RT)
        recipient :: BigInt = -1
    end

    @with_kw mutable struct LMTIncomes{RT<:Real}
        gross_earnings :: RT = zero(RT)
        net_earnings   :: RT = zero(RT)
        other_income   :: RT = zero(RT)
        total_income   :: RT = zero(RT)
        disregard :: RT = zero(RT)
        childcare :: RT = zero(RT)
        capital :: RT = zero(RT)
        tariff_income :: RT = zero(RT)
        disqualified_on_capital :: Bool = false
    end

    #
    @with_kw mutable struct LMTCanApplyFor
        ctc  :: Bool = false
        esa :: Bool = false
        hb  :: Bool = false
        is :: Bool = false
        jsa :: Bool = false
        pc  :: Bool = false
        sc  :: Bool = false
        wtc  :: Bool = false
        ctr :: Bool = false
    end

    @with_kw mutable struct LMTResults{RT<:Real}
        #ctc :: RT = zero(RT) XX
        #esa :: RT = zero(RT) x
        #hb  :: RT = zero(RT) x
        #is :: RT = zero(RT) x
        #jsa :: RT = zero(RT) x
        #pc  :: RT = zero(RT) x
        #sc   :: RT = zero(RT) x
        #wtc  :: RT = zero(RT)
        #ctr  :: RT = zero(RT) x

        ndds :: RT = zero(RT)
        mig  :: RT = zero(RT)
        mig_recipient :: BigInt = -1

        # total_benefits :: RT = zero(RT) #  hb and ctr

        # FIXME better name than MIG here
        # FIXME rename premia => premium everywhere here
        mig_premia :: RT = zero(RT)
        mig_allowances :: RT = zero(RT)
        mig_incomes = LMTIncomes{RT}()
        sc_incomes = LMTIncomes{RT}()

        ctc_elements :: RT = zero(RT)
        wtc_income  :: RT = zero(RT)
        wtc_elements :: RT = zero(RT)
        wtc_ctc_threshold :: RT = zero(RT)
        wtc_ctc_tapered_excess :: RT = zero(RT)
        wtc_recipient :: BigInt = -1
        cost_of_childcare :: RT = zero(RT)

        hb_passported :: Bool = false

        # TODO FIXME
        other_housing_costs :: RT = zero(RT)

        hb_premia :: RT = zero(RT)
        hb_allowances :: RT = zero(RT)
        hb_incomes = LMTIncomes{RT}()
        hb_eligible_rent :: RT = zero(RT)
        hb_recipient :: BigInt = -1

        ctr :: RT = zero(RT)
        ctr_passported :: Bool = false
        ctr_premia :: RT = zero(RT)
        ctr_allowances :: RT = zero(RT)
        ctr_incomes = LMTIncomes{RT}()
        ctr_eligible_amount :: RT = zero(RT)
        ctr_recipient :: BigInt = -1

        pc_premia :: RT = zero(RT)
        pc_allowances :: RT = zero(RT)
        pc_incomes = LMTIncomes{RT}()
        pc_recipient :: BigInt = -1

        premia = LMTPremiaSet()
        can_apply_for = LMTCanApplyFor()
    end

    @with_kw mutable struct NIResult{RT<:Real}
        above_lower_earnings_limit :: Bool = false
        # total_ni :: RT = 0.0
        class_1_primary    :: RT = 0.0
        class_1_secondary  :: RT = 0.0
        class_2   :: RT = 0.0
        class_3   :: RT = 0.0
        class_4   :: RT = 0.0
        assumed_gross_wage :: RT = 0.0
    end

    @with_kw mutable struct ITResult{RT<:Real}
        # total_tax :: RT = 0.0
        taxable_income :: RT = 0.0
        adjusted_net_income :: RT = 0.0
        total_income :: RT = 0.0
        allowance   :: RT = 0.0

        non_savings_tax :: RT = 0.0
        non_savings_band :: Integer = 0
        non_savings_income :: RT = 0.0
        non_savings_taxable :: RT = 0.0

        savings_tax :: RT = 0.0
        savings_band :: Integer = 0
        savings_income :: RT = 0.0
        savings_taxable :: RT = 0.0

        dividends_tax :: RT = 0.0
        dividend_band :: Integer = 0
        dividends_income :: RT = 0.0
        dividends_taxable :: RT = 0.0

        unused_allowance :: RT = 0.0
        mca :: RT = 0.0
        transferred_allowance :: RT = 0.0
        pension_eligible_for_relief :: RT = 0.0
        pension_relief_at_source :: RT = 0.0

        non_savings_thresholds :: RateBands = zeros(RT,0)
        savings_thresholds  :: RateBands = zeros(RT,0)
        dividend_thresholds :: RateBands = zeros(RT,0)

        savings_rates  :: RateBands = zeros(RT,0)
        dividend_rates :: RateBands = zeros(RT,0)

        personal_savings_allowance :: RT = 0.0
    end

    @with_kw mutable struct IndividualResult{RT<:Real}

       net_income :: RT = zero(RT)
       ni = NIResult{RT}()
       it = ITResult{RT}()
       metr :: RT = -12345.0
       replacement_rate :: RT = zero(RT)
       income = STBIncomes.make_a( RT );
    end

    function to_string( ir :: IndividualResult, depth=2 )::String
        s = to_md_table( ir, exclude=[:income], depth=depth )
        s *= #*repeat("#",depth)*"Incomes";
        s *= inctostr( ir.income )
        return s
    end

    @with_kw mutable struct BenefitUnitResult{RT<:Real} # FIXME make this a dynamic thing to match get_benefit_units
        income = STBIncomes.make_a( RT )
        net_income    :: RT = zero(RT)
        eq_net_income :: RT = zero(RT)
        legacy_mtbens = LMTResults{RT}()
        uc = UCResults{RT}()
        bencap = BenefitCapResults{RT}()
        other_benefits  :: RT = zero(RT)
        pers = Dict{BigInt,IndividualResult{RT}}() # FIXME name change to `people`
        adults = Pid_Array()
        route :: LegacyOrUC = legacy_bens
    end

    function to_string( br :: BenefitUnitResult, depth=1 )::String
        s = to_md_table( br, exclude=[:pers,:adults,:income], depth=depth )
        s *= "#### Benefit Unit Incomes "
        s *= inctostr(br.income)
        for pid in sort(collect(keys(br.pers)))
            s *= "### Individual Result; Person $(pid)"
            s *= to_string( br.pers[pid] )
        end
        return s
    end

    function has_any( bur :: BenefitUnitResult, things ... ) :: Bool
        for (pid,pers) in bur.pers
            if any_positive( pers.income, IncomesSet(things))
                return true
            end
        end
        return false
    end

    @with_kw mutable struct LocalTaxes{RT<:Real}
        # council_tax :: RT = zero(RT)
        # this isn't really used at present since LOCAL_TAXES
        # are in the incomes list, but we'll keep it around for modelling other types of local
        # tax in future.
        thing_just_to_make_with_kw_work :: RT = -99
    end

    function calc_net_income(incs::AbstractArray{T})::T where T
        # HB/CT treated seperately at household level
        # println( "incs="*inctostr( incs ))
        # println( ALL_INCOMES_EXCEPT_HOUSING_BENEFITS )
        # println( DIRECT_TAXES_AND_DEDUCTIONS )
        n :: T = isum(incs,
            ALL_INCOMES, # _EXCEPT_HOUSING_BENEFITS,
            deducted=DIRECT_TAXES_AND_DEDUCTIONS )
        # println( "n=$n")
        return n
    end

    @with_kw mutable struct HousingResult{RT<:Real}
        allowed_rooms :: Int = 0
        excess_rooms :: Int = 0
        allowed_rent :: RT = zero(RT) # FIXME this name
        gross_rent :: RT = zero(RT)
        # might not just be gross-allowed if we model other deductions
        rooms_rent_reduction :: RT = zero(RT) #
     end

    @with_kw mutable struct HouseholdResult{RT<:Real}
        income = STBIncomes.make_a( RT )

        bhc_net_income :: RT = zero(RT)
        eq_bhc_net_income :: RT = zero(RT)
        ahc_net_income :: RT = zero(RT)
        eq_ahc_net_income :: RT = zero(RT)

        net_housing_costs :: RT = zero(RT)
        housing = HousingResult{RT}()
        # FIXME note this is at the household level, which makes local income taxes, etc. akward. OK for now.
        local_tax = LocalTaxes{RT}()
        #
        # FIXME make this `people` to match the ModelHousehold
        # and adapt the get_benefit_units function
        #
        bus = Vector{BenefitUnitResult{RT}}(undef,0)
    end

    function get_net_income( hres :: HouseholdResult; target :: TargetBCIncomes ) :: Real
        if target == ahc_hh
            return hres.ahc_net_income
        elseif target == bhc_hh
            return hres.bhc_net_income
        elseif target == total_bens
            return isum(hres.income, BENEFITS )
        elseif target == total_taxes
            return isum(hres.income, INCOME_TAXES)
        end
    end

    function to_string( hr :: HouseholdResult, depth=0 )::String
        s = "# Results for Household"
        s *= to_md_table( hr, exclude=[:bus,:income], depth=depth )
        s *= "## HH Income"
        s *= inctostr( hr.income )
        s *= "\n\n"
        for bn in eachindex(hr.bus)
            s *= repeat("#",depth+1)*" Benefit Unit Result $(bn)"
            s *= to_string( hr.bus[bn], depth+1)
        end
        return s
    end

    """
    Test the individual incomes in hhr for some values
    """
    function has_any( hhr :: HouseholdResult, things ... ) :: Bool
        for bu in hhr.bus
            if has_any( bu, things ... )
                return true
            end
        end
        return false
    end

    function total( bur :: BenefitUnitResult{T}, which :: Incomes ) ::T where T
        t = zero(T)
        for (pid,pers) in bur.pers
            t += pers.income[which]
        end
        return t
    end

    function tozero!( bur :: BenefitUnitResult, which... )
        for (pid,pers) in bur.pers
            # println( pers.income )
            for w in which
                pers.income[w] = 0
            end
        end
    end

    function gross_pension_contributions( pres :: IndividualResult ) :: Real
        p = pres.income[PENSION_CONTRIBUTIONS_EMPLOYEE] +
            pres.it.pension_relief_at_source
        return max(0.0, p )
    end

    function gross_pension_contributions( bures :: BenefitUnitResult ) :: Real
        p = 0.0
        for pid in bures.adults
            p += gross_pension_contributions( bu.pers[pid])
        end
        return p;
    end

    function total( hhr :: HouseholdResult{T}, which :: Incomes ) :: T where T
        t = zero(T)
        for bu in hhr.bus
            t += total( bu, which )
        end
        return t
    end

    function add_to!( ni :: NIResult, ni2 :: NIResult )
        # ni.above_lower_earnings_limit += ni2.above_lower_earnings_limit
        # ni.total_ni += ni2.total_ni
        ni.class_1_primary    += ni2.class_1_primary
        ni.class_1_secondary  += ni2.class_1_secondary
        ni.class_2   += ni2.class_2
        ni.class_3   += ni2.class_3
        ni.class_4   += ni2.class_4
        ni.assumed_gross_wage += ni2.assumed_gross_wage
    end

    function add_to!( it :: ITResult, it2 :: ITResult )
        # it.total_tax += it2.total_tax
        it.taxable_income += it2.taxable_income
        it.adjusted_net_income += it2.adjusted_net_income
        it.total_income += it2.total_income
        it.allowance   += it2.allowance

        it.non_savings_tax += it2.non_savings_tax
        it.non_savings_band += it2.non_savings_band
        it.non_savings_income += it2.non_savings_income
        it.non_savings_taxable += it2.non_savings_taxable

        it.savings_tax += it2.savings_tax
        it.savings_band += it2.savings_band
        it.savings_income += it2.savings_income
        it.savings_taxable += it2.savings_taxable

        it.dividends_tax += it2.dividends_tax
        it.dividend_band += it2.dividend_band
        it.dividends_income += it2.dividends_income
        it.dividends_taxable += it2.dividends_taxable

        it.unused_allowance += it2.unused_allowance
        it.mca += it2.mca
        it.transferred_allowance += it2.transferred_allowance
        it.pension_eligible_for_relief += it2.pension_eligible_for_relief
        it.pension_relief_at_source += it2.pension_relief_at_source

        it.personal_savings_allowance += it2.personal_savings_allowance
    end

    """
    This is used to get aggregate taxable income for means-tested benefits;
    we don't really need bu/hh aggregated data in the results record other than total tax
    """
    function aggregate_tax( bu :: BenefitUnitResult{T}; include_children :: Bool = true ) :: Tuple where T
        pids = include_children ? keys( bu.pers ) : bu.adults
        it = ITResult{T}()
        ni = NIResult{T}()
        for pid in pids
            add_to!( it, bu.pers[pid].it )
            add_to!( ni, bu.pers[pid].ni )
        end
        return (it,ni)
    end

    function aggregate!( bures :: BenefitUnitResult{T} ) where T
        bures.income .= zero(T)
        for (pid,pers) in bures.pers
            bures.income .+= pers.income
        end
        bures.net_income = calc_net_income( bures.income )
    end

    function aggregate!( hh :: Household{T}, hres :: HouseholdResult{T} ) where T
        hres.income .= zero(T)
        for bu in hres.bus
            aggregate!( bu )
            hres.income .+= bu.income
        end
        #
        # FIXME it's possible we need BU net incomes as well
        #
        hres.bhc_net_income = calc_net_income( hres.income )
        # -
        #     hres.bus[1].uc.housing_element
        #    hres.income[HOUSING_BENEFIT] -
        #    hres.income[COUNCIL_TAX_BENEFIT]

        #
        # count pension relief at source as net income (but not as negative income tax)
        #
        for bu in hres.bus
            for (pid,pers) in bu.pers
                hres.bhc_net_income += pers.it.pension_relief_at_source
            end
        end
        if hres.bhc_net_income <= 0
            println("zero bhc_net_income for seq=$(hh.sequence); hid=$(hh.hid) year=$(hh.interview_year) ")
            println("income")
            println( inctostr( hres.income ))
            println( "bhc_net_income $(hres.bhc_net_income) HOUSING_BENEFIT=$(hres.income[HOUSING_BENEFIT]) COUNCIL_TAX_BENEFIT=$(hres.income[COUNCIL_TAX_BENEFIT]) hres.income[COUNCIL_TAX_BENEFIT]=$(hres.income[COUNCIL_TAX_BENEFIT])")
        end
        # FIXME this doesn't work with Universal Credit
        hres.net_housing_costs = hh.gross_rent +
            hres.income[LOCAL_TAXES] +
            hh.mortgage_payment +
            hh.other_housing_charges +
            hh.water_and_sewerage -
            hres.income[HOUSING_BENEFIT] -
            hres.income[COUNCIL_TAX_BENEFIT] - 0
            # hres.bus[1].uc.housing_element

        hres.ahc_net_income = hres.bhc_net_income -
            hh.gross_rent -
            hres.income[LOCAL_TAXES] -
            hh.mortgage_payment -
            hh.other_housing_charges -
            hh.water_and_sewerage

        # hres.net_housing_costs
        hres.eq_bhc_net_income = hres.bhc_net_income/hh.equivalence_scales.oecd_bhc
        hres.eq_ahc_net_income = hres.ahc_net_income/hh.equivalence_scales.oecd_ahc
    end

   function init_benefit_unit_result( T::Type, bu :: BenefitUnit ) :: BenefitUnitResult{T}
        # FIXME add an explicit head/spouse pid here
        bur = BenefitUnitResult{T}()
        bur.adults = bu.adults
        for pid in keys( bu.people )
            bur.pers[pid] = IndividualResult{T}()
            bur.pers[pid].income = map_incomes( bu.people[pid])

        end
        return bur
    end

    # create results that mirror some
    # allocation of people to benefit units
    # FIXME remove the type and use where RT
    function init_household_result( hh :: Household{T} ) :: HouseholdResult{T} where T
        bus = get_benefit_units(hh)
        hr = HouseholdResult{T}()
        for bu in bus
            push!( hr.bus, init_benefit_unit_result( T, bu ))
        end
        return hr
    end

    function get_indiv_result( hres :: HouseholdResult, pid :: BigInt  ) :: IndividualResult
        for bu in hres.bus
            if haskey( bu.pers, pid )
                return bu.pers[pid]
            end
        end
        @assert false "no pid $pid in household result"
        ## deliberate fail here
    end

    function map_incomes( pers :: Person{T}; include_calculated :: Bool=false ) :: MVector{INC_ARRAY_SIZE,T} where T
        out = MVector{INC_ARRAY_SIZE,T}( zeros(T,INC_ARRAY_SIZE ))
        incd = pers.income
        # println( "incd = $(incd)")
        if haskey(incd, Definitions.wages )
            out[WAGES] = incd[Definitions.wages]
        end
        if haskey(incd, Definitions.self_employment_income )
            out[SELF_EMPLOYMENT_INCOME] = incd[Definitions.self_employment_income]
        end
        if haskey(incd, Definitions.odd_jobs )
            out[ODD_JOBS] = incd[Definitions.odd_jobs]
        end
        if haskey(incd, Definitions.private_pensions )
            out[PRIVATE_PENSIONS] = incd[Definitions.private_pensions]
        end
        if haskey(incd, Definitions.national_savings )
            out[NATIONAL_SAVINGS] = incd[Definitions.national_savings]
        end
        if haskey(incd, Definitions.bank_interest )
            out[BANK_INTEREST] = incd[Definitions.bank_interest]
        end
        if haskey(incd, Definitions.stocks_shares )
            out[STOCKS_SHARES] = incd[Definitions.stocks_shares]
        end
        if haskey(incd, Definitions.individual_savings_account )
            out[INDIVIDUAL_SAVINGS_ACCOUNT] = incd[Definitions.individual_savings_account]
        end
        if haskey(incd, Definitions.property )
            out[PROPERTY] = incd[Definitions.property]
        end
        if haskey(incd, Definitions.royalties )
            out[ROYALTIES] = incd[Definitions.royalties]
        end
        if haskey(incd, Definitions.bonds_and_gilts )
            out[BONDS_AND_GILTS] = incd[Definitions.bonds_and_gilts]
        end
        if haskey(incd, Definitions.other_investment_income )
            out[OTHER_INVESTMENT_INCOME] = incd[Definitions.other_investment_income]
        end
        if haskey(incd, Definitions.other_income )
            out[OTHER_INCOME] = incd[Definitions.other_income]
        end
        if haskey(incd, Definitions.alimony_and_child_support_received )
            out[ALIMONY_AND_CHILD_SUPPORT_RECEIVED] = incd[Definitions.alimony_and_child_support_received]
        end
        if haskey(incd, Definitions.private_sickness_scheme_benefits )
            out[PRIVATE_SICKNESS_SCHEME_BENEFITS] = incd[Definitions.private_sickness_scheme_benefits]
        end
        if haskey(incd, Definitions.accident_insurance_scheme_benefits )
            out[ACCIDENT_INSURANCE_SCHEME_BENEFITS] = incd[Definitions.accident_insurance_scheme_benefits]
        end
        if haskey(incd, Definitions.hospital_savings_scheme_benefits )
            out[HOSPITAL_SAVINGS_SCHEME_BENEFITS] = incd[Definitions.hospital_savings_scheme_benefits]
        end
        if haskey(incd, Definitions.unemployment_or_redundancy_insurance )
            out[UNEMPLOYMENT_OR_REDUNDANCY_INSURANCE] = incd[Definitions.unemployment_or_redundancy_insurance]
        end
        if haskey(incd, Definitions.permanent_health_insurance )
            out[PERMANENT_HEALTH_INSURANCE] = incd[Definitions.permanent_health_insurance]
        end
        if haskey(incd, Definitions.any_other_sickness_insurance )
            out[ANY_OTHER_SICKNESS_INSURANCE] = incd[Definitions.any_other_sickness_insurance]
        end
        if haskey(incd, Definitions.critical_illness_cover )
            out[CRITICAL_ILLNESS_COVER] = incd[Definitions.critical_illness_cover]
        end
        if haskey(incd, Definitions.trade_union_sick_or_strike_pay )
            out[TRADE_UNION_SICK_OR_STRIKE_PAY] = incd[Definitions.trade_union_sick_or_strike_pay]
        end
        if haskey(incd, Definitions.health_insurance )
            out[HEALTH_INSURANCE] = incd[Definitions.health_insurance]
        end
        if haskey(incd, Definitions.alimony_and_child_support_paid )
            out[ALIMONY_AND_CHILD_SUPPORT_PAID] = incd[Definitions.alimony_and_child_support_paid]
        end
        if haskey(incd, Definitions.trade_unions_etc )
            out[TRADE_UNIONS_ETC] = incd[Definitions.trade_unions_etc]
        end
        if haskey(incd, Definitions.friendly_societies )
            out[FRIENDLY_SOCIETIES] = incd[Definitions.friendly_societies]
        end
        if haskey(incd, Definitions.work_expenses )
            out[WORK_EXPENSES] = incd[Definitions.work_expenses]
        end
        if haskey(incd, Definitions.avcs )
            out[AVCS] = incd[Definitions.avcs]
        end
        if haskey(incd, Definitions.other_deductions )
            out[OTHER_DEDUCTIONS] = incd[Definitions.other_deductions]
        end
        if haskey(incd, Definitions.loan_repayments )
            out[LOAN_REPAYMENTS] = incd[Definitions.loan_repayments]
        end
        if haskey(incd, Definitions.pension_contributions_employee )
            out[PENSION_CONTRIBUTIONS_EMPLOYEE] = incd[Definitions.pension_contributions_employee]
        end
        if haskey(incd, Definitions.pension_contributions_employer )
            out[PENSION_CONTRIBUTIONS_EMPLOYER] = incd[Definitions.pension_contributions_employer]
        end

        ### passed through benefits
        if haskey(incd, Definitions.other_benefits )
            out[OTHER_BENEFITS] = incd[Definitions.other_benefits]
        end
        if haskey(incd, Definitions.student_grants )
            out[STUDENT_GRANTS] = incd[Definitions.student_grants]
        end
        if haskey(incd, Definitions.student_loans )
            out[STUDENT_LOANS] = incd[Definitions.student_loans]
        end
        if haskey(incd, Definitions.free_school_meals )
            out[FREE_SCHOOL_MEALS] = incd[Definitions.free_school_meals]
        end
        if haskey(incd, Definitions.social_fund_loan_repayment_from_is_or_pc)
            out[SOCIAL_FUND_LOAN_REPAYMENT] = incd[Definitions.social_fund_loan_repayment_from_is_or_pc]
        end
        if haskey(incd, Definitions.social_fund_loan_repayment_from_jsa_or_esa)
            out[SOCIAL_FUND_LOAN_REPAYMENT] += incd[Definitions.social_fund_loan_repayment_from_jsa_or_esa]
        end
        if haskey(incd, Definitions.student_loan_repayments )
            out[STUDENT_LOAN_REPAYMENTS] = incd[Definitions.student_loan_repayments]
        end
        if haskey(incd, Definitions.armed_forces_compensation_scheme )
            out[ARMED_FORCES_COMPENSATION_SCHEME] = incd[Definitions.armed_forces_compensation_scheme]
        end
        if haskey(incd, Definitions.war_widows_or_widowers_pension )
            out[WAR_WIDOWS_PENSION] = incd[Definitions.war_widows_or_widowers_pension]
        end
        if haskey(incd, Definitions.severe_disability_allowance )
            out[SEVERE_DISABILITY_ALLOWANCE] = incd[Definitions.severe_disability_allowance]
        end
        if haskey(incd, Definitions.attendance_allowance )
            out[ATTENDANCE_ALLOWANCE] = incd[Definitions.attendance_allowance]
        end
        if haskey(incd, Definitions.foster_care_payments )
            out[FOSTER_CARE_PAYMENTS] = incd[Definitions.foster_care_payments]
        end
        if haskey(incd, Definitions.maternity_grant_from_social_fund )
            out[MATERNITY_GRANT] = incd[Definitions.maternity_grant_from_social_fund]
        end
        if haskey(incd, Definitions.funeral_grant_from_social_fund )
            out[FUNERAL_GRANT] = incd[Definitions.funeral_grant_from_social_fund]
        end
        if haskey(incd, Definitions.any_other_ni_or_state_benefit )
            out[ANY_OTHER_NI_OR_STATE_BENEFIT] = incd[Definitions.any_other_ni_or_state_benefit]
        end
        if haskey(incd, Definitions.friendly_society_benefits )
            out[FRIENDLY_SOCIETY_BENEFITS] = incd[Definitions.friendly_society_benefits]
        end
        if haskey(incd, Definitions.government_training_allowances )
            out[GOVERNMENT_TRAINING_ALLOWANCES] = incd[Definitions.government_training_allowances]
        end

        #
        #
        #

        if include_calculated
            if haskey(incd, Definitions.income_tax )
                out[INCOME_TAX] = incd[Definitions.income_tax]
            end
            if haskey(incd, Definitions.national_insurance )
                out[NATIONAL_INSURANCE] = incd[Definitions.national_insurance]
            end
            if haskey(incd, Definitions.local_taxes )
                out[LOCAL_TAXES] = incd[Definitions.local_taxes]
            end

            if haskey(incd, Definitions.care_insurance )
                out[CARE_INSURANCE] = incd[Definitions.care_insurance]
            end
            if haskey(incd, Definitions.child_benefit )
                out[CHILD_BENEFIT] = incd[Definitions.child_benefit]
            end
            if haskey(incd, Definitions.state_pension )
                out[STATE_PENSION] = incd[Definitions.state_pension]
            end
            if haskey(incd, Definitions.bereavement_allowance_or_widowed_parents_allowance_or_bereavement )
                out[BEREAVEMENT_ALLOWANCE] = incd[Definitions.bereavement_allowance_or_widowed_parents_allowance_or_bereavement]
            end
            if haskey(incd, Definitions.carers_allowance )
                out[CARERS_ALLOWANCE] = incd[Definitions.carers_allowance]
            end
            if haskey(incd, Definitions.industrial_injury_disablement_benefit )
                out[INDUSTRIAL_INJURY_BENEFIT] = incd[Definitions.industrial_injury_disablement_benefit]
            end
            if haskey(incd, Definitions.incapacity_benefit )
                out[INCAPACITY_BENEFIT] = incd[Definitions.incapacity_benefit]
            end
            if haskey(incd, Definitions.personal_independence_payment_daily_living )
                out[PERSONAL_INDEPENDENCE_PAYMENT_DAILY_LIVING] = incd[Definitions.personal_independence_payment_daily_living]
            end
            if haskey(incd, Definitions.personal_independence_payment_mobility )
                out[PERSONAL_INDEPENDENCE_PAYMENT_MOBILITY] = incd[Definitions.personal_independence_payment_mobility]
            end
            if haskey(incd, Definitions.dlaself_care )
                out[DLA_SELF_CARE] = incd[Definitions.dlaself_care]
            end
            if haskey(incd, Definitions.dlamobility )
                out[DLA_MOBILITY] = incd[Definitions.dlamobility]
            end
            if haskey(incd, Definitions.education_allowances )
                out[EDUCATION_ALLOWANCES] = incd[Definitions.education_allowances]
            end
            if haskey(incd, Definitions.maternity_allowance )
                out[MATERNITY_ALLOWANCE] = incd[Definitions.maternity_allowance]
            end
            if haskey(incd, Definitions.jobseekers_allowance )
                if pers.jsa_type == contributory_jsa
                    out[CONTRIB_JOBSEEKERS_ALLOWANCE] = incd[Definitions.jobseekers_allowance]
                elseif pers.jsa_type == income_related_jsa
                    out[NON_CONTRIB_JOBSEEKERS_ALLOWANCE] = incd[Definitions.jobseekers_allowance]
                elseif pers.jsa_type == both_jsa
                    out[NON_CONTRIB_JOBSEEKERS_ALLOWANCE] = incd[Definitions.jobseekers_allowance]/2
                    out[CONTRIB_JOBSEEKERS_ALLOWANCE] = incd[Definitions.jobseekers_allowance]/2
                else
                    @assert false "jsa is positive but jsa_type unset"
                end
            end
            if haskey(incd, Definitions.widows_payment )
                out[WIDOWS_PAYMENT] = incd[Definitions.widows_payment]
            end
            if haskey(incd, Definitions.winter_fuel_payments )
                out[WINTER_FUEL_PAYMENTS] = incd[Definitions.winter_fuel_payments]
            end
            if haskey(incd, Definitions.working_tax_credit )
                out[WORKING_TAX_CREDIT] = incd[Definitions.working_tax_credit]
            end
            if haskey(incd, Definitions.child_tax_credit )
                out[CHILD_TAX_CREDIT] = incd[Definitions.child_tax_credit]
            end
            if haskey(incd, Definitions.employment_and_support_allowance )
                if pers.esa_type == contributory_jsa
                    out[CONTRIB_EMPLOYMENT_AND_SUPPORT_ALLOWANCE] = incd[Definitions.employment_and_support_allowance]
                elseif pers.esa_type == income_related_jsa
                    out[NON_EMPLOYMENT_AND_SUPPORT_ALLOWANCE] = incd[Definitions.employment_and_support_allowance]
                elseif pers.esa_type == both_jsa
                    out[NON_CONTRIB_EMPLOYMENT_AND_SUPPORT_ALLOWANCE] = incd[Definitions.employment_and_support_allowance]/2
                    out[CONTRIB_EMPLOYMENT_AND_SUPPORT_ALLOWANCE] = incd[Definitions.employment_and_support_allowance]/2
                else
                    @assert false "jsa is positive but jsa_type unset"
                end
            end
            if haskey(incd, Definitions.income_support )
                out[INCOME_SUPPORT] = incd[Definitions.income_support]
            end
            if haskey(incd, Definitions.pension_credit )
                out[PENSION_CREDIT] = incd[Definitions.pension_credit]
            end
            # merged with pension credit in the frs, I think
            # if haskey(incd, Definitions.savings_credit )
            #    out[SAVINGS_CREDIT] = incd[Definitions.savings_credit]
            # end
            if haskey(incd, Definitions.housing_benefit )
                out[HOUSING_BENEFIT] = incd[Definitions.housing_benefit]
            end

            if haskey(incd, Definitions.extended_hb )
                out[HOUSING_BENEFIT] += incd[Definitions.extended_hb]
            end

            if haskey(incd, Definitions.working_tax_credit_lump_sum )
                out[WORKING_TAX_CREDIT] += incd[Definitions.working_tax_credit_lump_sum]
            end
            if haskey(incd, Definitions.child_tax_credit_lump_sum )
                out[CHILD_TAX_CREDIT] += incd[Definitions.child_tax_credit_lump_sum]
            end

            if haskey(incd, Definitions.universal_credit )
                out[UNIVERSAL_CREDIT] = incd[Definitions.universal_credit]
            end
            if haskey(incd, Definitions.guardians_allowance )
                out[GUARDIANS_ALLOWANCE] = incd[Definitions.guardians_allowance]
            end
            # not in the income list
            # if haskey(incd, Definitions.council_tax_rebate )
            #     out[COUNCIL_TAX_REBATE] = incd[Definitions.council_tax_rebate]
            # end
        end # include calculated
        return out
    end
end


#=
    Scenario reduction functions
=#
abstract type AbstractScenarios end

mutable struct Scenarios{T, O, I} <: AbstractScenarios
    demands::Vector{NamedTuple{(:t, :power),Tuple{T,O}}}
    generations::Vector{NamedTuple{(:t, :power, :cost), Tuple{T, O, I}}}
    storages::Vector{NamedTuple{(:cost,), Tuple{I}}}
    converters::Vector{NamedTuple{(:cost,), Tuple{I}}}
    grids::Vector{NamedTuple{(:cost_in, :cost_out), Tuple{O, O}}}
end

# Constructor
function Scenarios(mg::Microgrid, d::Dict{})
    # Utils to simplify the writting
    h, y, s = 1:mg.parameters.nh, 1:mg.parameters.ny, 1:mg.parameters.ns
    T, O, I = Array{DateTime,3}, Array{Float64, 3}, Array{Float64, 2}
    # Initialize
    demands = Vector{NamedTuple{(:t, :power),Tuple{T,O}}}(undef, length(mg.demands))
    generations = Vector{NamedTuple{(:t, :power, :cost), Tuple{T, O, I}}}(undef, length(mg.generations))
    storages = Vector{NamedTuple{(:cost,), Tuple{I}}}(undef, length(mg.storages))
    converters = Vector{NamedTuple{(:cost,), Tuple{I}}}(undef, length(mg.converters))
    grids = Vector{NamedTuple{(:cost_in, :cost_out), Tuple{O, O}}}(undef, length(mg.grids))
    # Demands
    for (k, a) in enumerate(mg.demands)
        if a.carrier isa Electricity
            demands[k] = (t = d["ld_E"].t[h, y, s], power = d["ld_E"].power[h, y, s])
        elseif a.carrier isa Heat
            demands[k] = (t = d["ld_H"].t[h, y, s], power = d["ld_H"].power[h, y, s])
        end
    end
    # Generation
    for (k, a) in enumerate(mg.generations)
        if a isa Solar
            generations[k] = (t = d["pv"].t[h, y, s], power = d["pv"].power[h, y, s], cost = d["pv"].cost[y, s])
        end
    end
    # Storages
    for (k, a) in enumerate(mg.storages)
        if a isa Liion
            storages[k] = (cost = d["liion"].cost[y, s],)
        elseif a isa ThermalStorage
            storages[k] = (cost = d["tes"].cost[y, s],)
        elseif a isa H2Tank
            storages[k] = (cost = d["h2tank"].cost[y, s],)
        end
    end
    # Converters
    for (k, a) in enumerate(mg.converters)
        if a isa Electrolyzer
            converters[k] = (cost = d["elyz"].cost[y, s],)
        elseif a isa FuelCell
            converters[k] = (cost = d["fc"].cost[y, s],)
        elseif a isa Heater
            converters[k] = (cost = d["heater"].cost[y, s],)
        end
    end
    # Grids
    for (k, a) in enumerate(mg.grids)
        if a.carrier isa Electricity
            grids[k] = (cost_in = d["grid"].cost_in[h, y, s], cost_out = d["grid"].cost_out[h, y, s])
        end
    end

    return Scenarios(demands, generations, storages, converters, grids)
end