text:
# V1.0
@test ! TS.is_comment_line( "" )
@test   TS.is_comment_line( "!" )
@test   TS.is_comment_line( "!comment" )

@test   TS.parse_comment_line( "!" ) == ""
@test   TS.parse_comment_line( "!comment" ) == "comment"

@test ! TS.is_option_line( "" )
@test   TS.is_option_line( "#" )
@test   TS.is_option_line( "#options" )

@test ! TS.is_frequency_unit_option( "" )
@test   TS.is_frequency_unit_option( "Hz" )
@test   TS.is_frequency_unit_option( "kHz" )
@test   TS.is_frequency_unit_option( "MHz" )
@test   TS.is_frequency_unit_option( "GHz" )
@test   TS.is_frequency_unit_option( "hz" )
@test   TS.is_frequency_unit_option( "khz" )
@test   TS.is_frequency_unit_option( "mhz" )
@test   TS.is_frequency_unit_option( "ghz" )
@test ! TS.is_frequency_unit_option( "uHz" )

@test TS.parse_frequency_unit_option( "Hz" )  ≈ 1e0
@test TS.parse_frequency_unit_option( "kHz" ) ≈ 1e3
@test TS.parse_frequency_unit_option( "MHz" ) ≈ 1e6
@test TS.parse_frequency_unit_option( "GHz" ) ≈ 1e9
@test TS.parse_frequency_unit_option( "hz" )  ≈ 1e0
@test TS.parse_frequency_unit_option( "khz" ) ≈ 1e3
@test TS.parse_frequency_unit_option( "mhz" ) ≈ 1e6
@test TS.parse_frequency_unit_option( "ghz" ) ≈ 1e9

@test ! TS.is_parameter_option( "" )
@test   TS.is_parameter_option( "S" )
@test   TS.is_parameter_option( "Y" )
@test   TS.is_parameter_option( "Z" )
@test   TS.is_parameter_option( "H" )
@test   TS.is_parameter_option( "G" )
@test   TS.is_parameter_option( "s" )
@test   TS.is_parameter_option( "y" )
@test   TS.is_parameter_option( "z" )
@test   TS.is_parameter_option( "h" )
@test   TS.is_parameter_option( "g" )
@test ! TS.is_parameter_option( "T" )

@test ! TS.is_format_option( "" )
@test   TS.is_format_option( "DB" )
@test   TS.is_format_option( "MA" )
@test   TS.is_format_option( "RI" )
@test   TS.is_format_option( "db" )
@test   TS.is_format_option( "ma" )
@test   TS.is_format_option( "ri" )
@test ! TS.is_format_option( "BLA" )

@test   TS.parse_option_line( "#" )               == Options( 1e9, :ScatteringParameters, :MagnitudeAngle,    50.0 )
@test   TS.parse_option_line( "#kHz" )            == Options( 1e3, :ScatteringParameters, :MagnitudeAngle,    50.0 )
@test   TS.parse_option_line( "# kHz" )           == Options( 1e3, :ScatteringParameters, :MagnitudeAngle,    50.0 )
@test   TS.parse_option_line( "#MHz Y" )          == Options( 1e6, :AdmittanceParameters, :MagnitudeAngle,    50.0 )
@test   TS.parse_option_line( "# GHz Z dB" )      == Options( 1e9, :ImpedanceParameters,  :DecibelAngle,      50.0 )
@test   TS.parse_option_line( "#Hz H RI R 1" )    == Options( 1.0, :HybridHParameters,    :RealImaginary,      1.0 )
@test   TS.parse_option_line( "# Hz G RI R 50" )  == Options( 1.0, :HybridGParameters,    :RealImaginary,     50.0 )
#
@test TS.parse_data( "100 0.99 -4", 1, Options( 1, :ScatteringParameters, :RealImaginary, 1 ) ).parameter ==
  fill( 0.99 -4im, 1, 1 )

@test TS.parse_data( "100 0.99 -4", 1, Options( 1, :ScatteringParameters, :MagnitudeAngle, 1 ) ).parameter ==
  fill( 0.987588409757226 - 0.06905890900668404im, 1, 1 )

@test TS.parse_data( "100 0.99 -4", 1, Options( 1, :ScatteringParameters, :DecibelAngle, 1 ) ).parameter ==
  fill( 1.117997390454142 - 0.07817799327562218im, 1, 1 )

@test TS.parse_data( "1 2 3 4 5 6 7 8 9", 2, Options( 1, :ScatteringParameters, :RealImaginary, 1 ) ).parameter ==
  [
    2.0+3im 6.0+7im;
    4.0+5im 8.0+9im
  ]

@test TS.parse_data( "1 2 3 4 5 6 7 8 9", 2, Options( 1, :ScatteringParameters, :MagnitudeAngle, 1 ) ).parameter ==
  [
    1.9972590695091477 + 0.10467191248588767im  5.955276909847932 + 0.7312160604308848im;
    3.984778792366982 + 0.34862297099063266im   7.901506724761102 + 1.251475720321847im
  ]

@test TS.parse_data( "1 2 3 4 5 6 7 8 9", 2, Options( 1, :ScatteringParameters, :DecibelAngle, 1 ) ).parameter ==
  [
    1.2572000982707197 + 0.06588706526478959im  1.9803899322373166 + 0.2431613082462919im;
    1.5788621953714619 + 0.1381325433646555im   2.4809609413011393 + 0.39294561015501567im
  ]

@test TS.parse_data(
  "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19", 3,
  Options( 1, :ScatteringParameters, :RealImaginary, 1 ) ).parameter ==
  [
    2.0 + 3.0im   4.0 + 5.0im   6.0 + 7.0im;
    8.0 + 9.0im   10.0 + 11.0im 12.0 + 13.0im;
    14.0 + 15.0im 16.0 + 17.0im 18.0 + 19.0im
  ]

@test TS.parse_data(
  "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19", 3,
  Options( 1, :ScatteringParameters, :MagnitudeAngle, 1 ) ).parameter ==
  [
    1.9972590695091477 + 0.10467191248588767im 3.984778792366982 + 0.34862297099063266im 5.955276909847932 + 0.7312160604308848im;
    7.901506724761102 + 1.251475720321847im 9.81627183447664 + 1.908089953765448im 11.692440777422823 + 2.69941265212638im;
    13.522961568046956 + 3.6234666314352904im 15.300876095408567 + 4.677947275563788im 17.019334360787703 + 5.860226780228821im
  ]

@test TS.parse_data(
  "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19", 3,
  Options( 1, :ScatteringParameters, :DecibelAngle, 1 ) ).parameter ==
  [
    1.2572000982707197 + 0.06588706526478959im 1.5788621953714619 + 0.1381325433646555im 1.9803899322373166 + 0.2431613082462919im;
    2.4809609413011393 + 0.39294561015501567im 3.1041777128305554 + 0.6033910234384192im 3.879037095636778 + 0.8955462775786209im;
    4.841096927669552 + 1.2971680122498457im 6.033875093362162 + 1.8447407441300518im 7.510521010548225 + 2.5860797741239554im
  ]

@test TS.parse_data(
     "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33", 4,
     Options( 1, :ScatteringParameters, :RealImaginary, 1 ) ).parameter ==
  [
    2.0 + 3.0im 4.0 + 5.0im 6.0 + 7.0im 8.0 + 9.0im;
    10.0 + 11.0im 12.0 + 13.0im 14.0 + 15.0im 16.0 + 17.0im;
    18.0 + 19.0im 20.0 + 21.0im 22.0 + 23.0im 24.0 + 25.0im;
    26.0 + 27.0im 28.0 + 29.0im 30.0 + 31.0im 32.0 + 33.0im
  ]

@test TS.parse_data(
     "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33", 4,
     Options( 1, :ScatteringParameters, :MagnitudeAngle, 1 ) ).parameter ==
  [
    1.9972590695091477 + 0.10467191248588767im 3.984778792366982 + 0.34862297099063266im 5.955276909847932 + 0.7312160604308848im 7.901506724761102 + 1.251475720321847im;
    9.81627183447664 + 1.908089953765448im 11.692440777422823 + 2.69941265212638im 13.522961568046956 + 3.6234666314352904im 15.300876095408567 + 4.677947275563788im;
    17.019334360787703 + 5.860226780228821im 18.671608529944034 + 7.167358990906005im 20.251106775953687+ 8.596084826764024im 21.751386888879598 + 10.142838281776786im;
    23.166169628897567 + 11.803752993228215im 24.48935179990308 + 13.574669366897437im 25.71501902106337 + 15.451142247301625im 26.83745817425357 + 17.428449120480867im
  ]

@test TS.parse_data(
     "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33", 4,
     Options( 1, :ScatteringParameters, :DecibelAngle, 1 ) ).parameter ==
  [
    1.2572000982707197 + 0.06588706526478959im 1.5788621953714619 + 0.1381325433646555im 1.9803899322373166 + 0.2431613082462919im 2.4809609413011393 + 0.39294561015501567im;
    3.1041777128305554 + 0.6033910234384192im 3.879037095636778 + 0.8955462775786209im 4.841096927669552 + 1.2971680122498457im 6.033875093362162 + 1.8447407441300518im;
    7.510521010548225 + 2.5860797741239554im 9.335804264972017 + 3.5836794954530027im 11.588469516911433 + 4.919013468341587im 14.36401041948883 + 6.698048060425835im;
    17.77791740104458 + 9.058301354842067im 21.969453750943302 + 12.17786706962086im 27.106010060371215 + 16.286933984241927im 33.388076683124744 + 21.68247052028196im
  ]

@test TS.parse_data( "1 2 3 4 5 6 7 8 9", 2, Options( 1, :ImpedanceParameters, :RealImaginary, 2 ) ).parameter ==
  [
    4.0+6.0im   12.0+14.0im;
    8.0+10.0im  16.0+18.0im;
  ]

@test TS.parse_data( "1 2 3 4 5 6 7 8 9", 2, Options( 1, :AdmittanceParameters, :RealImaginary, 0.5 ) ).parameter ==
  [
    4.0+6.0im   12.0+14.0im;
    8.0+10.0im  16.0+18.0im;
  ]

@test TS.parse_data( "1 2 0 4 5 6 7 0.5 0", 2, Options( 1, :HybridGParameters, :RealImaginary, 2 ) ).parameter ==
  [
    1          6.0+7.0im;
    4.0+5.0im  1;
  ]

@test TS.parse_data( "1 0.5 0 4 5 6 7 2 0", 2, Options( 1, :HybridHParameters, :RealImaginary, 2 ) ).parameter ==
  [
    1          6.0+7.0im;
    4.0+5.0im  1;
  ]

@test_throws ErrorException(
    "Wrong number of noise data values!"
  ) TS.parseNoiseData( [ 1.0 2.0 3.0 4.0 ] )

@test TS.parseNoiseData(
    [ 1.0 2.0 3.0 4.0 5.0 ]
  ) == NoiseDataPoint(
    1.0e9, 1.2589254117941673, 2.9926921507794724 + 0.2092694212323759im, 250.0
  )

@test TS.parseNoiseData(
    [ 1.0 2.0 3.0 4.0 5.0 ],
    Options( 1, :ScatteringParameters, :RealImaginary, 1 )
  ) == NoiseDataPoint(
    1.0, 1.2589254117941673, 2.9926921507794724 + 0.2092694212323759im, 5.0
  )

@test TS.parseNoiseData(
    [ 1.0 2.0 3.0 4.0 5.0 ],
    Options(),
    2
  ) == NoiseDataPoint(
    1.0e9, 1.2589254117941673, 2.9926921507794724 + 0.2092694212323759im, 5.0
  )

@test   parse_touchstone_string( "" ) == TouchstoneData( DataPoint[] )

@test   parse_touchstone_string( """
    !Test
    """ ) == TouchstoneData( DataPoint[], NoiseDataPoint[], Options(), [ "Test" ] )

# Example 9 (Version 1.0):
ts = parse_touchstone_string( """
  !1-port Z-parameter file, multiple frequency points
  # MHz Z MA R 75
  !freq magZ11 angZ11
  100     0.99    -4  !Comment
  200     0.80    -22
  300     0.707   -45
  400     0.40    -62
  500     0.01    -89
  """ )

@test freqs( ts ) ≈ collect( 1:5 ) * 100e6
@test mags( ts ) ≈ [ 74.25, 60.0, 53.025, 30.0, 0.75 ]
@test angs( ts ) ≈ [ -4, -22, -45, -62, -89 ]
@test ts.comments == [ "1-port Z-parameter file, multiple frequency points", "freq magZ11 angZ11", "Comment" ]
@test version( ts ) == "1.0"
@test ports( ts ) == 1
@test refs( ts ) == [ 75.0 ]

# Example 13 (Version 1.0):
ts = parse_touchstone_string( """
  !2-port S-parameter file, three frequency points
  # GHz S RI R 50.0
  !freq   ReS11   ImS11   ReS21   ImS21   ReS12   ImS12   ReS22   ImS22
  1.0000  0.3926  -0.1211 -0.0003 -0.0021 -0.0003 -0.0021 0.3926 -0.1211
  2.0000  0.3517  -0.3054 -0.0096 -0.0298 -0.0096 -0.0298 0.3517 -0.3054
  10.000  0.3419   0.3336 -0.0134  0.0379 -0.0134  0.0379 0.3419 0.3336
  """, 2 )

@test freqs( ts ) ≈ [ 1e9, 2e9, 10e9 ]

@test reals( ts, 1, 1 ) ≈ [ 0.3926, 0.3517, 0.3419 ]
@test imags( ts, 1, 1 ) ≈ [ -0.1211, -0.3054, 0.3336 ]

@test reals( ts, 2, 1 ) ≈ [ -0.0003, -0.0096, -0.0134 ]
@test imags( ts, 2, 1 ) ≈ [ -0.0021, -0.0298, 0.0379 ]

@test reals( ts, 1, 2 ) ≈ [ -0.0003, -0.0096, -0.0134 ]
@test imags( ts, 1, 2 ) ≈ [ -0.0021, -0.0298, 0.0379 ]

@test reals( ts, 2, 2 ) ≈ [ 0.3926, 0.3517, 0.3419 ]
@test imags( ts, 2, 2 ) ≈ [ -0.1211, -0.3054, 0.3336 ]

@test version( ts ) == "1.0"
@test ports( ts ) == 2
@test refs( ts ) == [ 50.0, 50.0 ]

ts = parse_touchstone_string( """
  # GHz S RI R 50.0
  1 1.11 1.11 1.21 1.21 1.12 1.12 1.22 1.22
  2 2.11 2.11 2.21 2.21 2.12 2.12 2.22 2.22
  """, 2 )
@test freqs( ts ) ≈ [ 1e9, 2e9 ]

@test reals( ts, 1, 1 ) ≈ [ 1.11, 2.11 ]
@test imags( ts, 1, 1 ) ≈ [ 1.11, 2.11 ]

@test reals( ts, 2, 1 ) ≈ [ 1.21, 2.21 ]
@test imags( ts, 2, 1 ) ≈ [ 1.21, 2.21 ]

@test reals( ts, 1, 2 ) ≈ [ 1.12, 2.12 ]
@test imags( ts, 1, 2 ) ≈ [ 1.12, 2.12 ]

@test reals( ts, 2, 2 ) ≈ [ 1.22, 2.22 ]
@test imags( ts, 2, 2 ) ≈ [ 1.22, 2.22 ]

@test version( ts ) == "1.0"
@test ports( ts ) == 2
@test refs( ts ) == [ 50.0, 50.0 ]

ts = parse_touchstone_string( """
  # GHz S RI R 50.0
  1   1.111 1.112 1.121 1.122 1.131 1.132
      1.211 1.212 1.221 1.222 1.231 1.232
      1.311 1.312 1.321 1.322 1.331 1.332
  2   2.111 2.112 2.121 2.122 2.131 2.132
      2.211 2.212 2.221 2.222 2.231 2.232
      2.311 2.312 2.321 2.322 2.331 2.332
  """, 3 )
@test freqs( ts ) ≈ [ 1e9, 2e9 ]

@test reals( ts, 1, 1 ) ≈ [ 1.111, 2.111 ]
@test imags( ts, 1, 1 ) ≈ [ 1.112, 2.112 ]

@test reals( ts, 1, 2 ) ≈ [ 1.121, 2.121 ]
@test imags( ts, 1, 2 ) ≈ [ 1.122, 2.122 ]

@test reals( ts, 1, 3 ) ≈ [ 1.131, 2.131 ]
@test imags( ts, 1, 3 ) ≈ [ 1.132, 2.132 ]

@test reals( ts, 2, 1 ) ≈ [ 1.211, 2.211 ]
@test imags( ts, 2, 1 ) ≈ [ 1.212, 2.212 ]

@test reals( ts, 2, 2 ) ≈ [ 1.221, 2.221 ]
@test imags( ts, 2, 2 ) ≈ [ 1.222, 2.222 ]

@test reals( ts, 2, 3 ) ≈ [ 1.231, 2.231 ]
@test imags( ts, 2, 3 ) ≈ [ 1.232, 2.232 ]

@test reals( ts, 3, 1 ) ≈ [ 1.311, 2.311 ]
@test imags( ts, 3, 1 ) ≈ [ 1.312, 2.312 ]

@test reals( ts, 3, 2 ) ≈ [ 1.321, 2.321 ]
@test imags( ts, 3, 2 ) ≈ [ 1.322, 2.322 ]

@test reals( ts, 3, 3 ) ≈ [ 1.331, 2.331 ]
@test imags( ts, 3, 3 ) ≈ [ 1.332, 2.332 ]

@test version( ts ) == "1.0"
@test ports( ts ) == 3
@test refs( ts ) == [ 50.0, 50.0, 50.0 ]

ts = parse_touchstone_string( """
  # GHz S RI R 50.0
  1   1.111 1.112 1.121 1.122 1.131 1.132 1.141 1.142
      1.211 1.212 1.221 1.222 1.231 1.232 1.241 1.242
      1.311 1.312 1.321 1.322 1.331 1.332 1.341 1.342
      1.411 1.412 1.421 1.422 1.431 1.432 1.441 1.442
  2   2.111 2.112 2.121 2.122 2.131 2.132 2.141 2.142
      2.211 2.212 2.221 2.222 2.231 2.232 2.241 2.242
      2.311 2.312 2.321 2.322 2.331 2.332 2.341 2.342
      2.411 2.412 2.421 2.422 2.431 2.432 2.441 2.442
  """, 4 )

@test freqs( ts ) ≈ [ 1e9, 2e9 ]

@test reals( ts, 1, 1 ) ≈ [ 1.111, 2.111 ]
@test imags( ts, 1, 1 ) ≈ [ 1.112, 2.112 ]

@test reals( ts, 1, 2 ) ≈ [ 1.121, 2.121 ]
@test imags( ts, 1, 2 ) ≈ [ 1.122, 2.122 ]

@test reals( ts, 1, 3 ) ≈ [ 1.131, 2.131 ]
@test imags( ts, 1, 3 ) ≈ [ 1.132, 2.132 ]

@test reals( ts, 1, 4 ) ≈ [ 1.141, 2.141 ]
@test imags( ts, 1, 4 ) ≈ [ 1.142, 2.142 ]

@test reals( ts, 2, 1 ) ≈ [ 1.211, 2.211 ]
@test imags( ts, 2, 1 ) ≈ [ 1.212, 2.212 ]

@test reals( ts, 2, 2 ) ≈ [ 1.221, 2.221 ]
@test imags( ts, 2, 2 ) ≈ [ 1.222, 2.222 ]

@test reals( ts, 2, 3 ) ≈ [ 1.231, 2.231 ]
@test imags( ts, 2, 3 ) ≈ [ 1.232, 2.232 ]

@test reals( ts, 2, 4 ) ≈ [ 1.241, 2.241 ]
@test imags( ts, 2, 4 ) ≈ [ 1.242, 2.242 ]

@test reals( ts, 3, 1 ) ≈ [ 1.311, 2.311 ]
@test imags( ts, 3, 1 ) ≈ [ 1.312, 2.312 ]

@test reals( ts, 3, 2 ) ≈ [ 1.321, 2.321 ]
@test imags( ts, 3, 2 ) ≈ [ 1.322, 2.322 ]

@test reals( ts, 3, 3 ) ≈ [ 1.331, 2.331 ]
@test imags( ts, 3, 3 ) ≈ [ 1.332, 2.332 ]

@test reals( ts, 3, 4 ) ≈ [ 1.341, 2.341 ]
@test imags( ts, 3, 4 ) ≈ [ 1.342, 2.342 ]

@test reals( ts, 4, 1 ) ≈ [ 1.411, 2.411 ]
@test imags( ts, 4, 1 ) ≈ [ 1.412, 2.412 ]

@test reals( ts, 4, 2 ) ≈ [ 1.421, 2.421 ]
@test imags( ts, 4, 2 ) ≈ [ 1.422, 2.422 ]

@test reals( ts, 4, 3 ) ≈ [ 1.431, 2.431 ]
@test imags( ts, 4, 3 ) ≈ [ 1.432, 2.432 ]

@test reals( ts, 4, 4 ) ≈ [ 1.441, 2.441 ]
@test imags( ts, 4, 4 ) ≈ [ 1.442, 2.442 ]

@test version( ts ) == "1.0"
@test ports( ts ) == 4
@test refs( ts ) == [ 50.0, 50.0, 50.0, 50.0 ]

ts = parse_touchstone_string( """
  # GHz S RI R 50.0
  1.0 1.11 1.11 1.12 1.12 1.13 1.13 1.14 1.14
  1.15 1.15
  1.21 1.21 1.22 1.22 1.23 1.23 1.24 1.24
  1.25 1.25
  1.31 1.31 1.32 1.32 1.33 1.33 1.34 1.34
  1.35 1.35
  1.41 1.41 1.42 1.42 1.43 1.43 1.44 1.44
  1.45 1.45
  1.51 1.51 1.52 1.52 1.53 1.53 1.54 1.54
  1.55 1.55
  2.0 2.11 2.11 2.12 2.12 2.13 2.13 2.14 2.14
  2.15 2.15
  2.21 2.21 2.22 2.22 2.23 2.23 2.24 2.24
  2.25 2.25
  2.31 2.31 2.32 2.32 2.33 2.33 2.34 2.34
  2.35 2.35
  2.41 2.41 2.42 2.42 2.43 2.43 2.44 2.44
  2.45 2.45
  2.51 2.51 2.52 2.52 2.53 2.53 2.54 2.54
  2.55 2.55
  """, 5 )
@test ts == TouchstoneData(
    [
      DataPoint( 1e9,  [
        1.11+1.11im 1.12+1.12im 1.13+1.13im 1.14+1.14im 1.15+1.15im;
        1.21+1.21im 1.22+1.22im 1.23+1.23im 1.24+1.24im 1.25+1.25im;
        1.31+1.31im 1.32+1.32im 1.33+1.33im 1.34+1.34im 1.35+1.35im;
        1.41+1.41im 1.42+1.42im 1.43+1.43im 1.44+1.44im 1.45+1.45im;
        1.51+1.51im 1.52+1.52im 1.53+1.53im 1.54+1.54im 1.55+1.55im;
      ] ),
      DataPoint( 2e9,  [
        2.11+2.11im 2.12+2.12im 2.13+2.13im 2.14+2.14im 2.15+2.15im;
        2.21+2.21im 2.22+2.22im 2.23+2.23im 2.24+2.24im 2.25+2.25im;
        2.31+2.31im 2.32+2.32im 2.33+2.33im 2.34+2.34im 2.35+2.35im;
        2.41+2.41im 2.42+2.42im 2.43+2.43im 2.44+2.44im 2.45+2.45im;
        2.51+2.51im 2.52+2.52im 2.53+2.53im 2.54+2.54im 2.55+2.55im;
      ] ),
    ],
    NoiseDataPoint[],
    Options( 1e9, :ScatteringParameters, :RealImaginary, 50.0 )
  )
@test version( ts ) == "1.0"
@test ports( ts ) == 5
@test refs( ts ) == [ 50.0, 50.0, 50.0, 50.0, 50.0 ]

ts = parse_touchstone_string( """
  # GHz S RI R 50.0
  1.0 1.11 1.11 1.12 1.12 1.13 1.13 1.14 1.14
  1.15 1.15 1.16 1.16 1.17 1.17 1.18 1.18
  1.19 1.19 1.11 1.11
  1.21 1.21 1.22 1.22 1.23 1.23 1.24 1.24
  1.25 1.25 1.26 1.26 1.27 1.27 1.28 1.28
  1.29 1.29 1.21 1.21
  1.31 1.31 1.32 1.32 1.33 1.33 1.34 1.34
  1.35 1.35 1.36 1.36 1.37 1.37 1.38 1.38
  1.39 1.39 1.31 1.31
  1.41 1.41 1.42 1.42 1.43 1.43 1.44 1.44
  1.45 1.45 1.46 1.46 1.47 1.47 1.48 1.48
  1.49 1.49 1.41 1.41
  1.51 1.51 1.52 1.52 1.53 1.53 1.54 1.54
  1.55 1.55 1.56 1.56 1.57 1.57 1.58 1.58
  1.59 1.59 1.51 1.51
  1.61 1.61 1.62 1.62 1.63 1.63 1.64 1.64
  1.65 1.65 1.66 1.66 1.67 1.67 1.68 1.68
  1.69 1.69 1.61 1.61
  1.71 1.71 1.72 1.72 1.73 1.73 1.74 1.74
  1.75 1.75 1.76 1.76 1.77 1.77 1.78 1.78
  1.79 1.79 1.71 1.71
  1.81 1.81 1.82 1.82 1.83 1.83 1.84 1.84
  1.85 1.85 1.86 1.86 1.87 1.87 1.88 1.88
  1.89 1.89 1.81 1.81
  1.91 1.91 1.92 1.92 1.93 1.93 1.94 1.94
  1.95 1.95 1.96 1.96 1.97 1.97 1.98 1.98
  1.99 1.99 1.91 1.91
  1.101 1.101 1.102 1.102 1.103 1.103 1.104 1.104
  1.105 1.105 1.106 1.106 1.107 1.107 1.108 1.108
  1.109 1.109 1.101 1.101
  """, 10 )
@test ts == TouchstoneData(
    [
    DataPoint( 1e9,  [
      1.11+1.11im 1.12+1.12im 1.13+1.13im 1.14+1.14im 1.15+1.15im 1.16+1.16im 1.17+1.17im 1.18+1.18im 1.19+1.19im 1.110+1.110im;
      1.21+1.21im 1.22+1.22im 1.23+1.23im 1.24+1.24im 1.25+1.25im 1.26+1.26im 1.27+1.27im 1.28+1.28im 1.29+1.29im 1.210+1.210im;
      1.31+1.31im 1.32+1.32im 1.33+1.33im 1.34+1.34im 1.35+1.35im 1.36+1.36im 1.37+1.37im 1.38+1.38im 1.39+1.39im 1.310+1.310im;
      1.41+1.41im 1.42+1.42im 1.43+1.43im 1.44+1.44im 1.45+1.45im 1.46+1.46im 1.47+1.47im 1.48+1.48im 1.49+1.49im 1.410+1.410im;
      1.51+1.51im 1.52+1.52im 1.53+1.53im 1.54+1.54im 1.55+1.55im 1.56+1.56im 1.57+1.57im 1.58+1.58im 1.59+1.59im 1.510+1.510im;
      1.61+1.61im 1.62+1.62im 1.63+1.63im 1.64+1.64im 1.65+1.65im 1.66+1.66im 1.67+1.67im 1.68+1.68im 1.69+1.69im 1.610+1.610im;
      1.71+1.71im 1.72+1.72im 1.73+1.73im 1.74+1.74im 1.75+1.75im 1.76+1.76im 1.77+1.77im 1.78+1.78im 1.79+1.79im 1.710+1.710im;
      1.81+1.81im 1.82+1.82im 1.83+1.83im 1.84+1.84im 1.85+1.85im 1.86+1.86im 1.87+1.87im 1.88+1.88im 1.89+1.89im 1.810+1.810im;
      1.91+1.91im 1.92+1.92im 1.93+1.93im 1.94+1.94im 1.95+1.95im 1.96+1.96im 1.97+1.97im 1.98+1.98im 1.99+1.99im 1.910+1.910im;
      1.101+1.101im 1.102+1.102im 1.103+1.103im 1.104+1.104im 1.105+1.105im 1.106+1.106im 1.107+1.107im 1.108+1.108im 1.109+1.109im 1.1010+1.1010im;
    ] ),
    ],
    NoiseDataPoint[],
    Options( 1e9, :ScatteringParameters, :RealImaginary, 50.0 )
  )
@test version( ts ) == "1.0"
@test ports( ts ) == 10
@test refs( ts ) == [ 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0 ]

@test_throws ErrorException(
    "V1.0: more than four complex values in one parameter line."
  ) ts = parse_touchstone_string( """
    # GHz S RI R 50.0
    1.0 1.11 1.11 1.12 1.12 1.13 1.13 1.14 1.14 1.15
    """, 5 )

@test_throws ErrorException(
    "V1.0: more than four complex values in one parameter line."
  ) ts = parse_touchstone_string( """
    # GHz S RI R 50.0
    1.0 1.11 1.11 1.12 1.12 1.13 1.13 1.14 1.14
    1.15 1.15
    1.21 1.21 1.22 1.22 1.23 1.23 1.24 1.24 1.25 1.25
    """, 5 )

@test_throws ErrorException(
    "G parameter format for 1-port files not allowed."
  ) parse_touchstone_string( """
    # GHz G RI R 50
    1.0 2.0 3.0
    """, 1
  )
@test_throws ErrorException(
    "H parameter format for 1-port files not allowed."
  ) parse_touchstone_string( """
    # GHz H RI R 50
    1.0 2.0 3.0
    """, 1
  )

# Example 918 (Version 1.0):
ts = parse_touchstone_string( """
  !2-port network, S-parameter and noise data
  !Default MA format, GHz frequencies, 50 ohm reference, S-parameters
  #
  ! NETWORK PARAMETERS
  2 .95 -26 3.57 157 .04 76 .66 -14
  22 .60 -144 1.30 40 .14 40 .56 -85
  ! NOISE PARAMETERS
  4  .7 .64 69 .38
  18 2.7 .46 -33 .40
  """, 2 )

@test noiseFreqs( ts ) == [ 4.0e9, 1.8e10 ]
@test minNoiseFigures( ts ) == [ 1.0839269140212036, 1.3645831365889245 ]
@test reflCoeffs( ts ) == [ 0.22935548770899225 + 0.5974914729582091im; 0.3857884612548951 - 0.2505339561069125im ]
@test effNoiseRess( ts ) == [ 19.0, 20.0 ]

# V2.0
@test ! TS.is_keyword_line( "!" )
@test ! TS.is_keyword_line( "#" )
@test   TS.is_keyword_line( "[Version] 2.0" )
@test   TS.is_keyword_line( "[Number Of Ports] 17" )

@test TS.parse_keyword_line( "[Version] 2.0" ) == ( :Version, 2 )
@test TS.parse_keyword_line( "[Number Of Ports] 17" ) == ( :NumberOfPorts, 17 )

ts = parse_touchstone_string( """
  [Version] 2.0
  # GHz S MA R 50
  [Number Of Ports] 1
  [Number Of Frequencies] 0
  """ )
@test ts == TouchstoneData(
    DataPoint[],
    NoiseDataPoint[],
    Options(),
    String[],
    Dict{ Symbol, Any }(
      :Version => 2,
      :NumberOfPorts => 1,
      :NumberOfFrequencies => 0,
    ),
  )
@test version( ts ) == "2.0"
@test ports( ts ) == 1
@test refs( ts ) == [ 50.0 ]

@test_throws ErrorException(
    "V2.0: [Version] 2.0 before option line expected."
  ) parse_touchstone_string( """
    # GHz S MA R 50
    [Version] 2.0
    """
  )

@test_throws ErrorException(
    "V2.0: Option line before [Number of Ports] keyword expected."
  ) parse_touchstone_string( """
    [Version] 2.0
    [Number Of Ports] 17
    # GHz S RI R 50
    """
  )

@test_throws ErrorException(
    "V2.0: [Version] keyword before [Two-Port Data Order] keyword expected."
  ) parse_touchstone_string( """
    # GHz S RI R 50
    [Number Of Ports] 2
    [Two-Port Data Order] 12_21
    """
  )

@test_throws ErrorException(
    "V2.0: Option line before [Number of Frequencies] keyword expected."
  ) parse_touchstone_string( """
    [Version] 2.0
    [Number Of Ports] 2
    [Number of Frequencies] 12
    """
  )

@test_throws ErrorException(
    "V2.0: [Number of Ports] keyword before [Number of Noise Frequencies] keyword expected."
  ) parse_touchstone_string( """
    [Version] 2.0
    # GHz S RI R 50
    [Number of Noise Frequencies] 11
    """
  )

@test_throws ErrorException(
    "V2.0: [Network Data] keyword after [Reference] keyword expected."
  ) parse_touchstone_string( """
    [Version] 2.0
    # GHz S RI R 50
    [Number Of Ports] 1
    [Number of Frequencies] 1
    [Network Data]
    [Reference] 11
    """
  )

@test_throws ErrorException(
    "V2.0: [Noise Data] keyword after [Network Data] keyword expected."
  ) parse_touchstone_string( """
    [Version] 2.0
    # GHz S RI R 50
    [Number Of Ports] 2
    [Number of Frequencies] 1
    [Reference] 11 12
    [Noise Data]
    [Network Data]
    1 2 3 4 5 6 7 8 9
    [End]
    """
  )

@test_throws ErrorException(
    "V2.0: [Number of Frequencies] keyword before [Network Data] keyword expected."
  ) parse_touchstone_string( """
    [Version] 2.0
    # GHz S RI R 50
    [Number Of Ports] 1
    [Reference] 11
    [Network Data]
    1.0 2.0 3.0
    [End]
    """
  )

ts = parse_touchstone_string( """
    [Version] 2.0
    !1-port Z-parameter file, multiple frequency points
    # MHz Z MA R 75
    [Number of Ports] 1
    [Number of Frequencies] 5
    !freq magZ11 angZ11
    [Network Data]
    100     0.99    -4  !Comment
    200     0.80    -22
    300     0.707   -45
    400     0.40    -62
    500     0.01    -89
    [End]
    """ )
@test freqs( ts ) ≈ collect( 1:5 ) * 100e6
@test mags( ts ) ≈ [ 0.99, 0.8, 0.707, 0.4, 0.01 ]
@test angs( ts ) ≈ [ -4, -22, -45, -62, -89 ]
@test ts.comments == [ "1-port Z-parameter file, multiple frequency points", "freq magZ11 angZ11", "Comment" ]
@test ts.keywordparams == Dict{ Symbol, Any }(
    :Version => 2,
    :NumberOfPorts => 1,
    :NumberOfFrequencies => 5,
    :NetworkData => [],
    :End  => [],
  )
@test version( ts ) == "2.0"
@test ports( ts ) == 1
@test refs( ts ) == [ 75.0 ]

@test_throws ErrorException(
    "V2.0: [Two-Port Data Order] keyword not allowed for 1 port data."
  ) parse_touchstone_string( """
    [Version] 2.0
    # GHz S RI R 50
    [Number Of Ports] 1
    [Number of Frequencies] 1
    [Two-Port Data Order] 12_21
    [Reference] 11
    [Network Data]
    1.0 2.0 3.0
    [End]
    """
  )

@test_throws ErrorException(
    "V2.0: [Two-Port Data Order] expected for two port data."
  ) parse_touchstone_string( """
    [Version] 2.0
    # GHz S RI R 50
    [Number Of Ports] 2
    [Number of Frequencies] 1
    [Network Data]
    1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0
    [End]
    """
  )

ts = parse_touchstone_string( """
  [Version] 2.0
  # GHz S RI R 50
  [Number Of Ports] 1
  [Number of Frequencies] 1
  [Reference] 11
  [Network Data]
  1.0 2.0 3.0
  [End]
  """ )
@test ts == TouchstoneData(
    DataPoint[
      DataPoint(
        1.0e9,
        fill( 2.0 + 3.0im, 1, 1 )
      )
    ],
    NoiseDataPoint[],
    Options( 1.0e9, :ScatteringParameters, :RealImaginary, 50.0 ),
    String[],
    Dict{Symbol,Any}(
      :NumberOfPorts => 1,
      :NumberOfFrequencies => 1,
      :Version => 2,
      :End => Any[],
      :NetworkData => Any[],
      :Reference => [ 11.0 ]
    )
  )
@test version( ts ) == "2.0"
@test ports( ts ) == 1
@test refs( ts ) == [ 11.0 ]

ts = parse_touchstone_string( """
  [Version] 2.0
  # GHz S RI R 50
  [Number Of Ports] 2
  [Number of Frequencies] 1
  [Two-Port Data Order] 12_21
  [Reference] 11 12
  [Network Data]
  1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0
  [End]
  """ )
@test ts == TouchstoneData(
    DataPoint[
      DataPoint(
        1.0e9,
        [ 2.0 + 3.0im 6.0 + 7.0im; 4.0 + 5.0im 8.0 + 9.0im ]
      )
    ],
    NoiseDataPoint[],
    Options( 1.0e9, :ScatteringParameters, :RealImaginary, 50.0 ),
    String[],
    Dict{Symbol,Any}(
      :NumberOfPorts => 2,
      :NumberOfFrequencies => 1,
      :Version => 2,
      :End => Any[],
      :NetworkData => Any[],
      :Reference => [ 11.0, 12.0 ],
      :TwoPortDataOrder => "12_21",
    )
  )
@test version( ts ) == "2.0"
@test ports( ts ) == 2
@test refs( ts ) == [ 11.0, 12.0 ]

@test_throws ErrorException(
    "V2.0: 1 parameters for [Reference] expected."
  ) parse_touchstone_string( """
    [Version] 2.0
    # GHz S RI R 50
    [Number Of Ports] 1
    [Number of Frequencies] 1
    [Reference] 11 12
    [Network Data]
    1.0 2.0 3.0
    [End]
    """
  )

@test_throws ErrorException(
    "V2.0: 1 parameters for [Reference] expected."
  ) parse_touchstone_string( """
    [Version] 2.0
    # GHz S RI R 50
    [Number Of Ports] 1
    [Number of Frequencies] 1
    [Reference]
    11 12
    [Network Data]
    1.0 2.0 3.0
    [End]
    """
  )

ts = parse_touchstone_string( """
  [Version] 2.0
  # GHz S RI R 50
  [Number Of Ports] 2
  [Number of Frequencies] 1
  [Two-Port Data Order] 12_21
  [Reference]
  11 12
  [Network Data]
  1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0
  [End]
  """ )
@test ts == TouchstoneData(
    DataPoint[
      DataPoint(
        1.0e9,
        [ 2.0 + 3.0im 6.0 + 7.0im; 4.0 + 5.0im 8.0 + 9.0im ]
      )
    ],
    NoiseDataPoint[],
    Options( 1.0e9, :ScatteringParameters, :RealImaginary, 50.0 ),
    String[],
    Dict{Symbol,Any}(
      :NumberOfPorts => 2,
      :NumberOfFrequencies => 1,
      :Version => 2,
      :End => Any[],
      :NetworkData => Any[],
      :Reference => [ 11.0, 12.0 ],
      :TwoPortDataOrder => "12_21",
    )
  )
@test version( ts ) == "2.0"
@test ports( ts ) == 2
@test refs( ts ) == [ 11.0, 12.0 ]

@test_throws ErrorException(
    "V2.0: Non empty or comment line found after [End] keyword."
  ) parse_touchstone_string( """
    [Version] 2.0
    # GHz S RI R 50
    [Number Of Ports] 2
    [Number of Frequencies] 1
    [Two-Port Data Order] 12_21
    [Reference]
    11 12
    [Network Data]
    1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0
    [End]
    1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0
    """
  )

@test_throws ErrorException(
    "V2.0: [NoiseData] keyword not allowed for 1 port data."
  ) parse_touchstone_string( """
    [Version] 2.0
    # GHz S RI R 50
    [Number Of Ports] 1
    [Number of Frequencies] 1
    [Network Data]
    1.0 2.0 3.0
    [Noise Data]
    1.0 2.0 3.0 4.0 5.0
    [End]
    """
  )

@test_throws ErrorException(
    "G parameter format for 1-port files not allowed."
  ) parse_touchstone_string( """
    [Version] 2.0
    # GHz G RI R 50
    [Number Of Ports] 1
    [Number of Frequencies] 1
    [Network Data]
    1.0 2.0 3.0
    [End]
    """
  )
@test_throws ErrorException(
    "H parameter format for 1-port files not allowed."
  ) parse_touchstone_string( """
    [Version] 2.0
    # GHz H RI R 50
    [Number Of Ports] 1
    [Number of Frequencies] 1
    [Network Data]
    1.0 2.0 3.0
    [End]
    """
  )

@test_throws ErrorException(
    "[Mixed-Mode Order] keyword for G parameter format not allowed."
  ) parse_touchstone_string( """
    [Version] 2.0
    # GHz G RI R 50
    [Number Of Ports] 2
    [Two-Port Data Order] 12_21
    [Number of Frequencies] 1
    [Mixed-Mode Order] S1 S2
    [Network Data]
    1 2 3 4 5 6 7 8 9
    [End]
    """
  )
@test_throws ErrorException(
    "[Mixed-Mode Order] keyword for H parameter format not allowed."
  ) parse_touchstone_string( """
    [Version] 2.0
    # GHz H RI R 50
    [Number Of Ports] 2
    [Two-Port Data Order] 12_21
    [Number of Frequencies] 1
    [Mixed-Mode Order] S1 S2
    [Network Data]
    1 2 3 4 5 6 7 8 9
    [End]
    """
  )

@test_throws ErrorException(
    "V2.0: Unknown parameter 'bla' for [Two-Port Data Order] keyword."
  ) parse_touchstone_string( """
    [Version] 2.0
    # GHz H RI R 50
    [Number Of Ports] 2
    [Two-Port Data Order] bla
    [Number of Frequencies] 1
    [Network Data]
    1 2 3 4 5 6 7 8 9
    [End]
    """
  )

ts = parse_touchstone_string( """
  [Version] 2.0
  # GHz S RI R 50
  [Number Of Ports] 2
  [Number of Frequencies] 1
  [Two-Port Data Order] 21_12
  [Network Data]
  1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0
  [End]
  """ )
@test ts == TouchstoneData(
    DataPoint[
      DataPoint(
        1.0e9,
        [ 2.0 + 3.0im 4.0 + 5.0im; 6.0 + 7.0im 8.0 + 9.0im ]
      )
    ],
    NoiseDataPoint[],
    Options( 1.0e9, :ScatteringParameters, :RealImaginary, 50.0 ),
    String[],
    Dict{Symbol,Any}(
      :NumberOfPorts => 2,
      :NumberOfFrequencies => 1,
      :Version => 2,
      :End => Any[],
      :NetworkData => Any[],
      :TwoPortDataOrder => "21_12",
    )
  )
@test version( ts ) == "2.0"
@test ports( ts ) == 2
@test refs( ts ) == [ 50.0, 50.0 ]


text:
dsts = Dict(
    0 => cu([3, 4, 5, 6, 7]),
    1 => cu([3 3 4 4 5;
             5 5 6 6 7]),
)
srcs = Dict(
    (0, true) => cu(ones(Int, 3, 4)),
    (0, false) => cu(ones(Int, 3) * collect(1:4)'),
    (1, true) => cu(ones(Int, 2, 3, 4)),
    (1, false) => cu([1, 2] .* reshape(ones(Int, 3) * collect(1:4)', 1,3,4)),
)
idxs = [
    cu([1 2 3 4;
        4 2 1 3;
        3 5 5 3]),  # integer index
    cu([(1,) (2,) (3,) (4,);
        (4,) (2,) (1,) (3,);
        (3,) (5,) (5,) (3,)]),  # tuple index
]
res = Dict(
    (+, 0, true) => cu([5, 6, 9, 8, 9]),
    (+, 1, true) => cu([5 5 8 6 7;
                        7 7 10 8 9]),
    (+, 0, false) => cu([4, 4, 12, 5, 5]),
    (+, 1, false) => cu([4 4 12 5 5;
                         8 8 24 10 10]),
    (-, 0, true) => cu([1, 2, 1, 4, 5]),
    (-, 1, true) => cu([1 1 0 2 3;
                        3 3 2 4 5]),
    (-, 0, false) => cu([-4, -4, -12, -5, -5]),
    (-, 1, false) => cu([-4 -4 -12 -5 -5;
                         -8 -8 -24 -10 -10]),
    (max, 0, true) => cu([3, 4, 5, 6, 7]),
    (max, 1, true) => cu([3 3 4 4 5;
                          5 5 6 6 7]),
    (max, 0, false) => cu([3, 2, 4, 4, 3]),
    (max, 1, false) => cu([3 2 4 4 3;
                           6 4 8 8 6]),
    (min, 0, true) => cu([1, 1, 1, 1, 1]),
    (min, 1, true) => cu([1 1 1 1 1;
                          1 1 1 1 1]),
    (min, 0, false) => cu([1, 2, 1, 1, 2]),
    (min, 1, false) => cu([1 2 1 1 2;
                           2 4 2 2 4]),
    (*, 0, true) => cu([3, 4, 5, 6, 7]),
    (*, 1, true) => cu([3 3 4 4 5;
                        5 5 6 6 7]),
    (*, 0, false) => cu([3, 4, 48, 4, 6]),
    (*, 1, false) => cu([3 4 48 4 6;
                        12 16 768 16 24]),
    (/, 0, true) => cu([0.75, 1., 0.3125, 1.5, 1.75]),
    (/, 1, true) => cu([0.75 0.75 0.25 1. 1.25;
                        1.25 1.25 0.375 1.5 1.75]),
    (/, 0, false) => cu([1//3, 1//4, 1//48, 1//4, 1//6]),
    (/, 1, false) => cu([1//3 1//4 1//48 1//4 1//6;
                         1//12 1//16 1//768 1//16 1//24]),
    (mean, 0, true) => cu([4., 5., 6., 7., 8.]),
    (mean, 1, true) => cu([4. 4. 5. 5. 6.;
                           6. 6. 7. 7. 8.]),
    (mean, 0, false) => cu([2, 2, 3, 2.5, 2.5]),
    (mean, 1, false) => cu([2. 2. 3. 2.5 2.5;
                            4. 4. 6. 5. 5.]),
)

types = [CuArray{Int32}, CuArray{Int64}, CuArray{Float32}, CuArray{Float64}]

@testset "scatter" begin
    for T = types
        @testset "$(T)" begin
            @testset "+" begin
                for idx = idxs, dims = [0, 1]
                    mutated = true
                    @test NNlib.scatter!(+, T(copy(dsts[dims])), T(srcs[(dims, mutated)]), idx) == T(res[(+, dims, mutated)])

                    mutated = false
                    @test NNlib.scatter(+, T(srcs[(dims, mutated)]), idx) == T(res[(+, dims, mutated)])
                end
            end

            @testset "-" begin
                for idx = idxs, dims = [0, 1]
                    mutated = true
                    @test NNlib.scatter!(-, T(copy(dsts[dims])), T(srcs[(dims, mutated)]), idx) == T(res[(-, dims, mutated)])

                    mutated = false
                    @test NNlib.scatter(-, T(srcs[(dims, mutated)]), idx) == T(res[(-, dims, mutated)])
                end
            end

            @testset "max" begin
                for idx = idxs, dims = [0, 1]
                    mutated = true
                    @test NNlib.scatter!(max, T(copy(dsts[dims])), T(srcs[(dims, mutated)]), idx) == T(res[(max, dims, mutated)])

                    mutated = false
                    @test NNlib.scatter(max, T(srcs[(dims, mutated)]), idx) == T(res[(max, dims, mutated)])
                end
            end

            @testset "min" begin
                for idx = idxs, dims = [0, 1]
                    mutated = true
                    @test NNlib.scatter!(min, T(copy(dsts[dims])), T(srcs[(dims, mutated)]), idx) == T(res[(min, dims, mutated)])

                    mutated = false
                    @test NNlib.scatter(min, T(srcs[(dims, mutated)]), idx) == T(res[(min, dims, mutated)])
                end
            end
        end
    end

    for T = [CuArray{Float32}, CuArray{Float64}]
        @testset "$(T)" begin
            @testset "*" begin
                for idx = idxs, dims = [0, 1]
                    mutated = true
                    @test NNlib.scatter!(*, T(copy(dsts[dims])), T(srcs[(dims, mutated)]), idx) == T(res[(*, dims, mutated)])

                    mutated = false
                    @test NNlib.scatter(*, T(srcs[(dims, mutated)]), idx) == T(res[(*, dims, mutated)])
                end
            end

            @testset "/" begin
                for idx = idxs, dims = [0, 1]
                    mutated = true
                    @test NNlib.scatter!(/, T(copy(dsts[dims])), T(srcs[(dims, mutated)].*2), idx) == T(res[(/, dims, mutated)])

                    mutated = false
                    @test NNlib.scatter(/, T(srcs[(dims, mutated)]), idx) == T(res[(/, dims, mutated)])
                end
            end

            @testset "mean" begin
                for idx = idxs, dims = [0, 1]
                    mutated = true
                    @test NNlib.scatter!(mean, T(copy(dsts[dims])), T(srcs[(dims, mutated)]), idx) == T(res[(mean, dims, mutated)])

                    mutated = false
                    @test NNlib.scatter(mean, T(srcs[(dims, mutated)]), idx) == T(res[(mean, dims, mutated)])
                end
            end
        end
    end
end

ext: jl


text:
"""
    RegressionModel <: StatisticalModel

Abstract supertype for all regression models.
"""
abstract type RegressionModel <: StatisticalModel end

"""
    fitted(model::RegressionModel)

Return the fitted values of the model.
"""
function fitted end

"""
    response(model::RegressionModel)

Return the model response (a.k.a. the dependent variable).
"""
function response end

"""
    responsename(model::RegressionModel)

Return the name of the model response (a.k.a. the dependent variable).
"""
function responsename end

"""
    meanresponse(model::RegressionModel)

Return the mean of the response.
"""
function meanresponse end

"""
    modelmatrix(model::RegressionModel)

Return the model matrix (a.k.a. the design matrix).
"""
function modelmatrix end

"""
    crossmodelmatrix(model::RegressionModel)

Return `X'X` where `X` is the model matrix of `model`.
This function will return a pre-computed matrix stored in `model` if possible.
"""
crossmodelmatrix(model::RegressionModel) = (x = modelmatrix(model); Symmetric(x' * x))

"""
    leverage(model::RegressionModel)

Return the diagonal of the projection matrix of the model.
"""
function leverage end

"""
    cooksdistance(model::RegressionModel)

Compute [Cook's distance](https://en.wikipedia.org/wiki/Cook%27s_distance)
for each observation in linear model `model`, giving an estimate of the influence
of each data point.
"""
function cooksdistance end

"""
    residuals(model::RegressionModel)

Return the residuals of the model.
"""
function residuals end

"""
    predict(model::RegressionModel, [newX])

Form the predicted response of `model`. An object with new covariate values `newX` can be supplied,
which should have the same type and structure as that used to fit `model`; e.g. for a GLM
it would generally be a `DataFrame` with the same variable names as the original predictors.
"""
function predict end

"""
    predict!

In-place version of [`predict`](@ref).
"""
function predict! end

"""
    dof_residual(model::RegressionModel)

Return the residual degrees of freedom of the model.
"""
function dof_residual end

max_stars_repo_name: JuliaStats/StatsAPI

max_issues_repo_name: JuliaStats/StatsAPI

max_forks_repo_name: JuliaStats/StatsAPI


text:
importall Bukdu
importall Bukdu.Octo
importall Tag

type CafeController <: ApplicationController
end

type User
    name::String
    age::Int
    description::String
    happiness::Float64
end

user = User("foo bar", 20, "", 0.5)

include("layout.jl")

function post_result(c::CafeController)
    changeset = change(c, user)
    changed = isempty(changeset.changes) ? "
no changes
" : ""
    render(HTML/Layout, """

$(changeset.model)

$(changeset.changes)

$changed

    """)
end

function input_form(form)
    form_for(form, action=post_result, method=post) do f
         """

  Name: $(text_input(f, :name))

    Age: $(select(f, :age, 18:30))

    Happiness: $(text_input(f, :happiness))

$(textarea(f, :description, placeholder="enter description"))

$(submit("Submit"))
"""
    end
end

function index(::CafeController)
    form = change(user)
    render(HTML/Layout, input_form(form))
end

Router() do
    get("/", CafeController, index)
    post("/post_result", CafeController, post_result)
end

Endpoint() do
    plug(Plug.Static, at= "/", from=normpath(dirname(@__FILE__), "public"); try_index_html=false)
    plug(Plug.Logger)
    plug(Router)
end

Bukdu.start(8080)

# wait()

# Bukdu.stop()


text:
# This file is a part of Julia. License is MIT: https://julialang.org/license

include("pkg.jl")
include("resolve.jl")

max_stars_repo_name: djsegal/julia-fork

max_issues_repo_name: djsegal/julia-fork

max_forks_repo_name: djsegal/julia-fork


text:
# AUTO GENERATED FILE - DO NOT EDIT

export keyedfilebrowser

"""
    keyedfilebrowser(;kwargs...)

A KeyedFileBrowser component.
KeyedFileBrowser is a component that renders a file browser based on a entry JSON.
The files and folders can be selected and the selected path can be
accessed via the `selectedPath` property.
Keyword arguments:
- `id` (String; optional): The ID of this component, used to identify dash components
in callbacks. The ID needs to be unique across all of the
components in an app.
- `files` (optional): The file tree to be displayed in the browser.
Each entry in the array must be an object with the `key`
property, which specifies it's location in the tree. Other
properties are `modified` and `size`.
To be able to receive the path selection feedback, you must
enter keys for both files and folders.. files has the following type: Array of lists containing elements 'key', 'modified', 'size'.
Those elements have the following types:
  - `key` (String; required)
  - `modified` (Real; optional)
  - `size` (Real; optional)s
- `selectedPath` (String; optional): The current selected path in the browser.
"""
function keyedfilebrowser(; kwargs...)
        available_props = Symbol[:id, :files, :selectedPath]
        wild_props = Symbol[]
        return Component("keyedfilebrowser", "KeyedFileBrowser", "dash_cool_components", available_props, wild_props; kwargs...)
end

max_stars_repo_name: Tauffer-Consulting/dash-cool-components

max_issues_repo_name: Tauffer-Consulting/dash-cool-components

max_forks_repo_name: Tauffer-Consulting/dash-cool-components


text:
module Types

using  ..Default
using ..Gadgetron.MRD
include("AcquisitionBucket.jl")
include("ReconData.jl")
end


text:
export Spectrum, AlphaSpectrum
export AlphaSpectrum
export example_alpha_spectrum, to_histogram

abstract type Spectrum end

find_peak(s::Spectrum) = []
slice(s::Spectrum) = Spectrum()
to_histogram(s::Spectrum) = nothing

@static if @isdefined(DEBUG) || @isdefined(TEST) ALPHA_SPECTRUM_TEST_FILE = "Alpha2.csv" end

"""spectrum from a solid state sensor"""
struct AlphaSpectrum <: Spectrum
    channels::Vector{Int}
    energies::Vector{Float64}
    numchannels::Int32

    "constructor for default AlphaSpectrum"
    function AlphaSpectrum()
        chs = zeros(Int32,1024)
        e = zeros(1024)
        numchs = 512
        new(chs,e,512)
    end

    "alpha spectrum from a DataFrameRow instance"
    function AlphaSpectrum(row::DataFrameRow)
        numcolumns = length(row)
        # @show numcolumns
        indexfirstch = 4
        numchs = numcolumns - indexfirstch + 1
        chs = [row[i] for i=indexfirstch:numcolumns]
        RANGE_MEV = 10_000_000;
        b = convert(Float64,RANGE_MEV) / convert(Float64,numcolumns)
        es = [b * i for i=1:numcolumns-3]
        new(chs,es,numchs)
    end
end

# abstract type SpectrumSource end
# get_spectra(source::SpectrumSource) = Vector{Spectrum}
# alphaDataFrame = CSV.File("/home/b/Data/Spectra/Alpha2.csv"; header=false) |> DataFrame
# return array of alpha spectra
"""read and return an array of alpha spectra from a CSV file"""
function read_alpha_spectrum_file(file)
    frame = CSV.File(file; header=false) |> DataFrame
    num_rows = nrow(frame)
    ret_alpha_spects = [AlphaSpectrum(frame[i,:]) for i in 1:num_rows]
    return ret_alpha_spects
end

function to_histogram(as::AlphaSpectrum)
    len = as.numchannels;
    edges = collect(1:len+1)
    return hist = StatsBase.Histogram(edges,as.channels)
end

function example_alpha_spectrum()
    asdatafile = joinpath(DetectAlpha.TEST_DATA_FOLDER,ALPHA_SPECTRUM_TEST_FILE)
    return read_alpha_spectrum_file(asdatafile)
end


text:
#a = rc("font", family="Times New Roman", size=16)
#println("a = ", a)

"""
Plots each step of a simulation.

`visualize(m, x, f, p, n_steps=10; pause_time=0.3)`

where
* `m` is a `SearchDomain`
* `x` is a `Vehicle`
* `f` is a subtype of `AbstractFilter`
* `p` is a subtype of `Policy`
* `n_steps` is the number of steps to simulate
* `pause_time` is the time to wait between steps

An alternative call uses the `SimUnit` type:

`visualize(m::SearchDomain, su::SimUnit; pause_time=0.3)`
"""
function visualize(m::SearchDomain,
                   x::Vehicle,
                   f::AbstractFilter,
                   p::Policy,
                   n_steps::Int=10;
                   pause_time::Real = 0.3
                  )

    tc = StepThreshold(n_steps)
    su = SimUnit(x, f, p, tc)
    visualize(m, su; pause_time=pause_time)
end

# doesn't reset the filter, vehicle, and policy
# assume SimUnit comes in clean and ready to go
function visualize(m::SearchDomain, uav::SimUnit;
                   pause_time=0.3,
                   alpha=0.1,
                   show_mean::Bool=false,
                   save_gif::Bool=true
                  )

    frames = Frames(MIME("image/png"), fps=5)

    #println("rc_context = ", rc_context())
    #rc("font", family="Times New Roman", size=16)
    #rc("text", usetext=true)

    figure("Simulation")
    plot(m, uav.f, uav.x, alpha=alpha)
    #title("i = 0")
    title("\$t\$ = 0 s")
    push!(frames, gcf())

    # What was the cost to getting this first observation?
    cost_sum = get_cost(uav, m)

    # before doing anything else, we observe
    #  and update filter once
    o = observe(m, uav.x)
    update!(uav, o)

    # This was our first step; steps count number of observations
    step_count = 1

    # plot if need be
    plot(m, uav.f, uav.x, alpha=alpha)
    #title("i = $(step_count)")
    title("\$t\$ = $(step_count) s")
    push!(frames, gcf())

    while !is_complete(uav.f, uav.tc, step_count)
        # act
        #tic()
        a = action(m, uav, o)
        #ta = toq()
        #println("time = ", ta)
        act!(m, uav.x, a)

        move_target!(m)

        # get cost and update step count
        cost_val = get_cost(uav, m, a)
        cost_sum += cost_val
        step_count += 1

        # observe and update
        o = observe(m, uav.x)
        update!(uav, o)

        # plot if need be
        pause(pause_time)
        figure("Simulation")
        cla()
        plot(m, uav.f, uav.x, alpha=alpha, show_mean=show_mean)
        #max_b = maximum(uav.f.b)
        #title("i = $(step_count), cost = $(round(cost_val,3))")
        #title("\$t\$ = $(step_count) s, cost = $(round(cost_val,3))")
        title("\$t\$ = $(step_count) s")
        push!(frames, gcf())
    end
    write("temp.mp4", frames)
    write("temp.gif", frames)

    return cost_sum
end

# visualize function for multiple UAVs at once
function visualize(m::SearchDomain, vsu::Vector{SimUnit};
                   pause_time=0.3,
                   alpha=0.2,
                   show_mean::Bool=false
                  )

    #println("rc_context = ", rc_context())
    #rc("font", family="Times New Roman", size=16)
    #rc("text", usetext=true)

    # What was the cost to getting this first observation?
    # TODO: commented out on 8/08/2018, put it back soon
    #cost_sum = get_cost(uav, m)

    # before doing anything else, we observe
    #  and update filter once
    o_vals = []
    for uav in vsu
        o = observe(m, uav.x)
        push!(o_vals, o)
        update!(uav, o)
    end

    # This was our first step; steps count number of observations
    step_count = 1

    # plot if need be
    figure("Simulation")
    plot(m, vsu, alpha=alpha)
    title("i = $(step_count)")

    #while !is_complete(uav.f, uav.tc, step_count)
    while !is_complete(vsu, step_count)
        # act

        for (uav_idx,uav) in enumerate(vsu)
            a = action(m, uav, o_vals[uav_idx])
            act!(m, uav.x, a)
        end

        move_target!(m)

        # get cost and update step count
        # TODO: handle costs for the entire group
        #cost_val = get_cost(uav, m, a)
        #cost_sum += cost_val
        step_count += 1

        # observe and update
        o_vals = []
        for uav in vsu
            o = observe(m, uav.x)
            push!(o_vals, o)
            update!(uav, o)
        end

        # plot if need be
        pause(pause_time)
        figure("Simulation")
        cla()
        plot(m, vsu, alpha=alpha, show_mean=show_mean)
        title("i = $(step_count)")
    end

end


text:
"""
```
binarize(Polysegment(), img)
```

Uses the *polynomial segmentation* technique to group the image pixels
into two categories (foreground and background).

# Details

The approach involves constructing a univariate second-degree polynomial
such that the two roots of the polynomial represent the graylevels
of two cluster centers (i.e the foreground and background). Pixels are then
assigned to the foreground or background depending on which cluster center
is closest.

# Arguments

The function argument is described in more detail below.

##  `img`

An `AbstractArray` representing an image. The image is automatically converted
to `Gray`.

# Example

Binarize the "cameraman" image in the `TestImages` package.

```julia
using TestImages, ImageBinarization

img = testimage("cameraman")
img_binary = binarize(Polysegment(), img)
```

## Reference

[1] R. E. Vidal, "Generalized Principal Component Analysis (GPCA): An Algebraic Geometric Approach to Subspace Clustering and Motion Segmentation." Order No. 3121739, University of California, Berkeley, Ann Arbor, 2003.
"""
function binarize(algorithm::Polysegment,  img::AbstractArray{T}) where T <: Gray
  # Construct data matrix for second-degree polynomial (Equation 2.3) in [1].
  x = vec(img)
  Lₙ = hcat(ones(length(x)), x, x.^2)
  F = svd(Lₙ)
  c = F.Vt[end,:]
  p = Poly(vec(c))
  μ₁, μ₂ = sort(roots(p))
  # Binarize the image.
  img₀₁ = zeros(Gray{Bool}, axes(img))
  for i in CartesianIndices(img)
    val = img[i]
    img₀₁[i] = (val-μ₁)^2 < (val-μ₂)^2 ? 0 : 1
  end
  img₀₁
end

function binarize(algorithm::Polysegment,  img::AbstractArray{T}) where T <: Colorant
  binarize(Polysegment(), Gray.(img))
end


text:
module Brick

using Base.Iterators
using LinearAlgebra
using Scapin.Elasticity
using Scapin.Grid

"""
    integrate(f, h)

Return the `d`-dimensional integral of `f` over `(0, h[1]) × (0, h[2]) × … × (0, h[d])`.

Uses 2-point Gauss-Legendre integration (tensorized over the `d` dimensions). `f` must
take a 1-dimensional array of size `d` as unique input. If `avg` is `true`, the
function returns the `N`-dimensional average.

"""
function integrate(f, h::NTuple{d,T}; avg = false) where {d,T<:Number}
    ξ = [-1 / √3, 1 / √3]
    weight = (avg ? one(T) : prod(h)) / 2^d
    return weight * sum(f, product((ξ .* h_ / 2 for h_ in h)...))
end

"""
    shape(x, h)

Return the value of the shape functions of the element, at the specified point.

This function returns a `d`-dimensional array `N`, such that `N[p]` is the shape
function associated with node `p`, evaluated at `x`. In particular, `N[p]`
evaluated at node `q` is `δ[p, q]` (Kronecker).

"""
function shape(x::NTuple{d,T}, h::NTuple{d,T}) where {d,T<:Number}
    ξ = 2 .* x ./ h
    ℒ = cell_vertices(d)
    return [prod((1 + (-1)^p[i] * ξ[i]) / 2 for i = 1:d) for p in ℒ]
end

"""
    gradient_operator(x, h)

Return the gradient operator at the specified point.

This function returns a `(d+1)` dimensional array `D` of size `(d, 2, 2, …)`.
If  `i` is the index of a component and `p` the `CartesianIndex` of the node, then
`D[i, p]` is the partial derivative of `N[p]` w.r.t. `x[i]`, evaluated at `x`.

`h` is the size of the brick element.

"""
function gradient_operator(x::NTuple{d,T}, h::NTuple{d,T}) where {d,T<:Number}
    ξ = 2 .* x ./ h
    ℒ = cell_vertices(d)
    return [
        prod(j == i ? (-1)^n[j] / h[j] : (1 + (-1)^n[j] * ξ[j]) / 2 for j = 1:d) for
        i = 1:d, n in ℒ
    ]
end

"""
    avg_gradient_operator(h)

Return the cell-average of the gradient operator.

"""
function avg_gradient_operator(h::NTuple{d,T}) where {d,T<:Number}
    return integrate(x -> gradient_operator(x, h), h, avg = true)
end

"""
    strain_displacement_operator(x, h)

Return the strain-displacement operator for the `d`-dimensional brick element of
size `h`, evaluated at point `x`.

This function returns a `(d+3)` dimensional array `B` of size `(d, d, 2, …, 2, d)`.
If `p` is the `CartesianIndex` of the node, and `i`, `j`, `k` are component
indices then, the interpolated `(i, j)` component of the strain at `x` reads

```
ε[i, j] = Σₖ Σₚ B[i, j, k, p] * u[k, p].
```

"""
function strain_displacement_operator(x::NTuple{d,T}, h::NTuple{d,T}) where {d,T<:Number}
    ℒ = cell_vertices(d)
    D = gradient_operator(x, h)
    B = zeros(T, d, d, d, fill(2, d)...)
    for i = 1:d, j = 1:d, p ∈ ℒ
        B[i, j, i, p] += D[j, p] / 2
        B[i, j, j, p] += D[i, p] / 2
    end
    return B
end

"""
    avg_strain_displacement_operator(h)

Return the cell average of the strain-displacement operator.

"""
function avg_strain_displacement_operator(h::NTuple{d,T}) where {d,T<:Number}
    return integrate(x -> strain_displacement_operator(x, h), h, avg = true)
end

"""
    stiffness_operator(h, C)

Return the stifness operator for the brick element of size `h`, and Hooke material `C`.

The stiffness operator `K` delivers the strain energy associated to the nodal
displacements `u`

```
U = u[i, p] * K[i, p, j, q] * u[j, q] / 2,
```

where `i, j ∈ {1, …, d}` are component indices and `p, q ∈ CartesianIndices(1:2, …, 1:2)`.

"""
function stiffness_operator(h::NTuple{d,T}, C::Hooke{d,T}) where {d,T<:Number}
    ℒ = cell_vertices(d)
    function f(x)
        D = gradient_operator(x, h)
        B = strain_displacement_operator(x, h)
        σε = Array{T}(undef, d, fill(2, d)..., d, fill(2, d)...)
        for i = 1:d, p ∈ ℒ, j = 1:d, q ∈ ℒ
            σε[i, p, j, q] =
                C.λ * D[i, p] * D[j, q] +
                2C.μ * sum(B[h, k, i, p] * B[h, k, j, q] for h = 1:d, k = 1:d)
        end
        return σε
    end
    integrate(f, h)
end

"""
    global_strain_displacement_operator(N, h)

Return the global strain-displacement operator for periodic boundary conditions.

The grid is defined by its size `N` and its spacing `h`.

The global strain-displacement operator `B` is a `d+2`-dimensional array of size
`(d, d, N[1], …, N[d])`, such that the average strain within element `p` reads

```
ε[i, j, p] = B[i, j, p, k, q] * u[k, q],
```

where

- `i, j, k ∈ {1, …, d}`: component indices,
- `p, q ∈ CartesianIndices(1:N[1], …, 1:N[d])`: node indices,
- `ε[i, j, p]`: `(i, j)`-th component of the average strain in element `p`,
- `u[k, q]`: `i`-th component of the displacement of node `q`.

!!! note

    Assembly of the global strain-displacement operator is done under the
    assumption of periodicity.

"""
function global_strain_displacement_operator(
    N::NTuple{d,Int},
    h::NTuple{d,T},
) where {d,T<:Number}
    Be = avg_strain_displacement_operator(h)
    B = zeros(T, d, d, N..., d, N...)
    𝓛 = cell_vertices(d) # Local node indices
    for p ∈ CartesianIndices(N)
        𝒢 = cell_vertices(p, N) # global node indices
        for q ∈ 𝓛
            B[:, :, p, :, 𝒢[q]] += Be[:, :, :, q]
        end
    end
    return B
end

"""
    global_stiffness_operator(N, h, μ, ν)

Return the global stiffness operator for periodic, homogeneous elasticity.

The grid size is `N`, the cell size is `h`. The constitutive material is
homogeneous, elastic linear and isotropic with stiffness `C`.

The global stiffness operator `K` is a `2d+2`-dimensional array of size
`(d, N[1], …, N[d], d, N[1], …, N[d])`, such that the strain energy of the
system reads

```
U = u[i, p] * K[i, p, j, q] * u[j, q] / 2,
```

where

- `i, j ∈ {1, …, d}`: component indices,
- `p, q ∈ CartesianIndices(1:N[1], …, 1:N[d])`: node indices,
- `u[i, p]`: `i`-th component of the displacement of node `p`.

!!! note

    Assembly of the global stiffness opertor is done under the assumption of
    periodicity.

"""
function global_stiffness_operator(
    N::NTuple{d,Int},
    h::NTuple{d,T},
    C::Hooke{d,T},
) where {d,T<:Number}
    Ke = stiffness_operator(h, C)
    K = zeros(T, d, N..., d, N...)
    𝓛 = cell_vertices(d) # Local node indices
    for e ∈ CartesianIndices(N)
        𝒢 = cell_vertices(e, N) # global node indices
        for m ∈ 𝓛, n ∈ 𝓛
            K[:, 𝒢[m], :, 𝒢[n]] += Ke[:, m, :, n]
        end
    end
    return K
end

export integrate,
    shape,
    gradient_operator,
    avg_gradient_operator,
    strain_displacement_operator,
    avg_strain_displacement_operator,
    stiffness_operator,
    global_strain_displacement_operator,
    global_stiffness_operator
end


text:
module FoldingTrees

using REPL.TerminalMenus
using AbstractTrees

export Node, toggle!, fold!, unfold!, isroot, count_open_leaves, next, prev, nodes

include("foldingtree.jl")
include("abstracttrees.jl")

if isdefined(TerminalMenus, :ConfiguredMenu)
    include("treemenu.jl")
end

end # module


text:
module day3

const ⨸ = mod1
readInput() = reduce(hcat, map.(x -> x == '.' ? 0 : 1, collect.(readlines(joinpath(@__DIR__, "input.txt")))))

function part1(input, slope = (3, 1))
    width, height = size(input)
    nbTrees = 0
    pos = (1, 1)
    while pos[2] <= height
        nbTrees += input[pos...]
        pos = pos .+ slope
        pos = (pos[1] ⨸ width, pos[2])
    end
    nbTrees
end

part2(input) = reduce(*, (part1(input, slope) for slope in ((1,1), (3,1), (5,1), (7,1), (1,2))))

end


text:
export
    ident,
    illum,
    illum_pl02,
    illum_plid_pl02,
    illumf,
    illumg,
    ilumin,
    inedpl,
    inelpl,
    inrypl,
    insrtc!,
    insrtc,
    insrtd!,
    insrtd,
    insrti!,
    insrti,
    inter,
    intmax,
    intmin,
    invert,
    invort,
    isordv,
    isrchc,
    isrchd,
    isrchi,
    isrot,
    iswhsp

function _ident()
    matrix = Array{SpiceDouble}(undef, 3, 3)
    ccall((:ident_c, libcspice), Cvoid, (Ref{SpiceDouble},), matrix)
    matrix
end

@deprecate ident() Array{Float64}(LinearAlgebra.I, 3, 3)

"""
    illumf(method, target, ilusrc, et, fixref, abcorr, obsrvr, spoint)

Compute the illumination angles - phase, incidence, and emission - at a specified point on a
target body. Return logical flags indicating whether the surface point is visible from the
observer's position and whether the surface point is illuminated.

The target body's surface is represented using topographic data provided by DSK files, or by a
reference ellipsoid.

The illumination source is a specified ephemeris object.

### Arguments ###

- `method`: Computation method
- `target`: Name of target body
- `ilusrc`: Name of illumination source
- `et`: Epoch in TDB seconds past J2000 TDB
- `fixref`: Body-fixed, body-centered target body frame
- `abcorr`: Aberration correction flag
- `obsrvr`: Name of observing body
- `spoint`: Body-fixed coordinates of a target surface point

### Output ###

- `trgepc`: Target surface point epoch
- `srfvec`: Vector from observer to target surface point
- `phase`: Phase angle at the surface point
- `incdnc`: Source incidence angle at the surface point
- `emissn`: Emission angle at the surface point
- `visibl`: Visibility flag (`true` if visible)
- `lit`: Illumination flag (`true` if illuminated)

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/illumf_c.html)
"""
function illumf(method, target, ilusrc, et, fixref, abcorr, obsrvr, spoint)
    @checkdims 3 spoint
    trgepc = Ref{SpiceDouble}()
    srfvec = Array{SpiceDouble}(undef, 3)
    phase = Ref{SpiceDouble}()
    incdnc = Ref{SpiceDouble}()
    emissn = Ref{SpiceDouble}()
    visibl = Ref{SpiceBoolean}()
    lit = Ref{SpiceBoolean}()
    ccall((:illumf_c, libcspice), Cvoid,
          (Cstring, Cstring, Cstring, SpiceDouble, Cstring, Cstring, Cstring, Ref{SpiceDouble},
           Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble},
           Ref{SpiceDouble}, Ref{SpiceBoolean}, Ref{SpiceBoolean}),
          method, target, ilusrc, et, fixref, abcorr, obsrvr, spoint, trgepc, srfvec, phase, incdnc,
          emissn, visibl, lit)
    handleerror()
    trgepc[], srfvec, phase[], incdnc[], emissn[], Bool(visibl[]), Bool(lit[])
end

"""
    illumg(method, target, ilusrc, et, fixref, obsrvr, spoint, abcorr)

Find the illumination angles (phase, incidence, and emission) at a specified surface point of a
target body.

The surface of the target body may be represented by a triaxial ellipsoid or by topographic data
provided by DSK files.

The illumination source is a specified ephemeris object.

### Arguments ###

- `method`: Computation method.
- `target`: Name of target body.
- `ilusrc`: Name of illumination source.
- `et`: Epoch in ephemeris seconds past J2000 TDB.
- `fixref`: Body-fixed, body-centered target body frame.
- `obsrvr`: Name of observing body.
- `spoint`: Body-fixed coordinates of a target surface point.
- `abcorr`: Aberration correction.

### Output ###

- `trgepc`: Sub-solar point epoch.
- `srfvec`: Vector from observer to sub-solar point.
- `phase`: Phase angle at the surface point.
- `incdnc`: Solar incidence angle at the surface point.
- `emissn`: Emission angle at the surface point.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/illumg_c.html)
"""
function illumg(method, target, ilusrc, et, fixref, obsrvr, spoint; abcorr="NONE")
    trgepc = Ref{SpiceDouble}(0)
    srfvec = Array{SpiceDouble}(undef, 3)
    phase  = Ref{SpiceDouble}(0)
    incdnc = Ref{SpiceDouble}(0)
    emissn = Ref{SpiceDouble}(0)
    ccall((:illumg_c, libcspice), Cvoid,
          (Cstring, Cstring, Cstring, SpiceDouble, Cstring, Cstring, Cstring, Ref{SpiceDouble},
           Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble},
           Ref{SpiceDouble}),
          method, target, ilusrc, et, fixref, abcorr, obsrvr, spoint, trgepc, srfvec, phase,
          incdnc, emissn)
    handleerror()
    trgepc[], srfvec, phase[], incdnc[], emissn[]
end

@deprecate illum illumin
@deprecate illum_pl02 illumin
@deprecate illum_plid_pl02 illumin

"""
    ilumin(method, target, et, fixref, obsrvr, spoint, abcorr)

Find the illumination angles (phase, solar incidence, and emission) at a specified surface point
of a target body.

### Arguments ###

- `method`: Computation method
- `target`: Name of target body
- `et`: Epoch in ephemeris seconds past J2000 TDB
- `fixref`: Body-fixed, body-centered target body frame
- `obsrvr`: Name of observing body
- `spoint`: Body-fixed coordinates of a target surface point
- `abcorr`: Aberration correction

### Output ###

- `trgepc`: Sub-solar point epoch
- `srfvec`: Vector from observer to sub-solar point
- `phase`: Phase angle at the surface point
- `incdnc`: Solar incidence angle at the surface point
- `emissn`: Emission angle at the surface point

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/ilumin_c.html)
"""
function ilumin(method, target, et, fixref, obsrvr, spoint; abcorr="NONE")
    trgepc = Ref{SpiceDouble}()
    srfvec = Array{SpiceDouble}(undef, 3)
    phase  = Ref{SpiceDouble}()
    incdnc = Ref{SpiceDouble}()
    emissn = Ref{SpiceDouble}()
    ccall((:ilumin_c, libcspice), Cvoid,
          (Cstring, Cstring, SpiceDouble, Cstring, Cstring, Cstring, Ref{SpiceDouble},
           Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble},
           Ref{SpiceDouble}),
          method, target, et, fixref, abcorr, obsrvr, spoint, trgepc, srfvec, phase, incdnc, emissn)
    handleerror()
    trgepc[], srfvec, phase[], incdnc[], emissn[]
end

"""
    inedpl(a, b, c, plane)

Find the intersection of a triaxial ellipsoid and a plane.

### Arguments ###

- `a`: Length of ellipsoid semi-axis lying on the x-axis
- `b`: Length of ellipsoid semi-axis lying on the y-axis
- `c`: Length of ellipsoid semi-axis lying on the z-axis
- `plane`: Plane that intersects ellipsoid

### Output ###

- `ellipse`: Intersection ellipse

Returns `nothing` if no ellipse could be found.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/inedpl_c.html)
"""
function inedpl(a, b, c, plane)
    ellipse = Ref{Ellipse}()
    found = Ref{SpiceBoolean}()
    ccall((:inedpl_c, libcspice), Cvoid,
          (SpiceDouble, SpiceDouble, SpiceDouble, Ref{Plane}, Ref{Ellipse}, Ref{SpiceBoolean}),
          a, b, c, plane, ellipse, found)
    handleerror()
    Bool(found[]) || return nothing
    ellipse[]
end

"""
    inelpl(ellips, plane)

Find the intersection of an ellipse and a plane.

### Arguments ###

- `ellips`: An ellipse
- `plane`: A plane

### Output ###

- `nxpts`: Number of intersection points of ellipse and plane
- `xpt1`, `xpt2`: Intersection points

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/inelpl_c.html)
"""
function inelpl(ellips, plane)
    nxpts = Ref{SpiceInt}()
    xpt1 = Array{SpiceDouble}(undef, 3)
    xpt2 = Array{SpiceDouble}(undef, 3)
    ccall((:inelpl_c, libcspice), Cvoid,
          (Ref{Ellipse}, Ref{Plane}, Ref{SpiceInt}, Ref{SpiceDouble}, Ref{SpiceDouble}),
          ellips, plane, nxpts, xpt1, xpt2)
    handleerror()
    Int(nxpts[]), xpt1, xpt2
end

"""
    inrypl(vertex, dir, plane)

Find the intersection of a ray and a plane.

### Arguments ###

- `vertex`, `dir`: Vertex and direction vector of ray
- `plane`: A plane

### Output ###

- `nxpts`: Number of intersection points of ray and plane
- `xpt1`, `xpt2`: Intersection points

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/inrypl_c.html)
"""
function inrypl(vertex, dir, plane)
    @checkdims 3 vertex dir
    nxpts = Ref{SpiceInt}()
    xpt = Array{SpiceDouble}(undef, 3)
    ccall((:inrypl_c, libcspice), Cvoid,
          (Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{Plane}, Ref{SpiceInt}, Ref{SpiceDouble}),
          vertex, dir, plane, nxpts, xpt)
    handleerror()
    Int(nxpts[]), xpt
end

"""
    insrtc!(set, item)

Insert an item into a character set.

### Arguments ###

- `set`: Insertion set
- `item`: Item to be inserted

### Output ###

Returns the updated set.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/insrtc_c.html)
"""
function insrtc!(set, item)
    ccall((:insrtc_c, libcspice), Cvoid, (Cstring, Ref{Cell{SpiceChar}}), item, set.cell)
    set
end

@deprecate insrtc insrtc!

"""
    insrtd!(set, item)

Insert an item into a double set.

### Arguments ###

- `set`: Insertion set
- `item`: Item to be inserted

### Output ###

Returns the updated set.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/insrtd_c.html)
"""
function insrtd!(set, item)
    ccall((:insrtd_c, libcspice), Cvoid, (SpiceDouble, Ref{Cell{SpiceDouble}}), item, set.cell)
    set
end

@deprecate insrtd insrtd!

"""
    insrti!(set, item)

Insert an item into an integer set.

### Arguments ###

- `set`: Insertion set
- `item`: Item to be inserted

### Output ###

Returns the updated set.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/insrti_c.html)
"""
function insrti!(set, item)
    ccall((:insrti_c, libcspice), Cvoid, (SpiceInt, Ref{Cell{SpiceInt}}), item, set.cell)
    set
end

@deprecate insrti insrti!

"""
    inter(a, b)

Intersect two sets of any data type to form a third set.

### Arguments ###

- `a`: First input set
- `b`: Second input set

### Output ###

Returns intersection of a and b.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/inter_c.html)
"""
function inter(a::SpiceCell{T}, b::SpiceCell{T}) where {T}
    n = card(a) + card(b)
    l = max(a.cell.length, b.cell.length)
    out = SpiceCell{T}(n, l)
    ccall((:inter_c, libcspice), Cvoid,
              (Ref{Cell{T}}, Ref{Cell{T}}, Ref{Cell{T}}),
              a.cell, b.cell, out.cell)
    handleerror()
    out
end

function _intmax()
    ccall((:intmax_c, libcspice), SpiceInt, ())
end

@deprecate intmax() typemax(Cint)

"""
    intmax()

!!! warning "Deprecated"
    Use `typemax(Cint)` instead.
"""
intmax

function _intmin()
    ccall((:intmin_c, libcspice), SpiceInt, ())
end

@deprecate intmin() typemin(Cint)

"""
    intmin()

!!! warning "Deprecated"
    Use `typemin(Cint)` instead.
"""
intmin

function _invert(matrix)
    out = Array{SpiceDouble}(undef, 3, 3)
    ccall((:invert_c, libcspice), Cvoid,
          (Ref{SpiceDouble}, Ref{SpiceDouble}),
          matrix, out)
    out
end

@deprecate invert inv

"""
    invert(matrix)

!!! warning "Deprecated"
    Use `inv(matrix)` instead.
"""
invert

function _invort(matrix)
    out = Array{SpiceDouble}(undef, 3, 3)
    ccall((:invort_c, libcspice), Cvoid,
          (Ref{SpiceDouble}, Ref{SpiceDouble}),
          matrix, out)
    out
end

@deprecate invort inv

"""
    invort(matrix)

!!! warning "Deprecated"
    Use `inv(matrix)` instead.
"""
invort

function _isordv(vec)
    vec_ = SpiceInt.(vec .- 1)
    n = length(vec)
    res = ccall((:isordv_c, libcspice), SpiceBoolean,
                (Ref{SpiceInt}, SpiceInt),
                vec_, n)
    Bool(res[])
end

@deprecate isordv isperm

"""
    isordv(vec)

!!! warning "Deprecated"
    Use `isperm(vec)` instead.
"""
isordv

function _isrchc(value, array)
    array_, m, n = chararray(array)
    res = ccall((:isrchc_c, libcspice), SpiceInt,
                (Cstring, SpiceInt, SpiceInt, Ref{SpiceChar}),
                value, m, n, array_)
    handleerror()
    res[] + 1
end

@deprecate isrchc(value, array) findfirst(array .== item)

"""
    isrchc(value, array)

!!! warning "Deprecated"
    Use `findfirst(array .== value)` instead.
"""
isrchc

function _isrchd(value, array)
    n = length(array)
    res = ccall((:isrchd_c, libcspice), SpiceBoolean,
                (SpiceDouble, SpiceInt, Ref{SpiceDouble}),
                value, n, array)
    handleerror()
    res[] + 1
end

@deprecate isrchd(value, array) findfirst(array .== item)

"""
    isrchd(value, array)

!!! warning "Deprecated"
    Use `findfirst(array .== value)` instead.
"""
isrchd

function _isrchi(value, array)
    array_ = SpiceInt.(array)
    n = length(array)
    res = ccall((:isrchi_c, libcspice), SpiceBoolean,
                (SpiceInt, SpiceInt, Ref{SpiceInt}),
                value, n, array_)
    handleerror()
    res[] + 1
end

@deprecate isrchi(value, array) findfirst(array .== item)

"""
    isrchi(value, array)

!!! warning "Deprecated"
    Use `findfirst(array .== value)` instead.
"""
isrchi

"""
    isrot(m, ntol, dtol)

Indicate whether a 3x3 matrix is a rotation matrix.

### Arguments ###

- `m`: A matrix to be tested
- `ntol`: Tolerance for the norms of the columns of `m`
- `dtol`: Tolerance for the determinant of a matrix whose columns are the unitized columns of `m`

### Output ###

Returns `true` if `m` is a rotation matrix.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/isrot_c.html)
"""
function isrot(m, ntol, dtol)
    @checkdims 3 3 m
    res = ccall((:isrot_c, libcspice), SpiceBoolean,
                (Ref{SpiceDouble}, SpiceDouble, SpiceDouble),
                m, ntol, dtol)
    handleerror()
    Bool(res[])
end

function _iswhsp(str)
    res = ccall((:iswhsp_c, libcspice), SpiceBoolean,
                (Cstring,),
                str)
    handleerror()
    Bool(res[])
end

@deprecate iswhsp(str) isempty(strip(str))

"""
    iswhsp(str)

!!! warning "Deprecated"
    Use `isempty(strip(str))` instead.
"""
iswhsp


text:
module Brainfuck

# Import external packages
using Match

# Include source files
include("brainfuck.jl")

# Export symbols
export brainfuck

end # module