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

module ArithmeticTest

using Test
using Dates

@testset "Time arithmetic" begin
    a = DatesPlus.Time(23, 59, 59)
    b = DatesPlus.Time(11, 59, 59)
    @test DatesPlus.CompoundPeriod(a - b) == DatesPlus.Hour(12)
end
@testset "Wrapping arithmetic for Months" begin
    # This ends up being trickier than expected because
    # the user might do 2014-01-01 + Month(-14)
    # monthwrap figures out the resulting month
    # when adding/subtracting months from a date
    @test DatesPlus.monthwrap(1, -14) == 11
    @test DatesPlus.monthwrap(1, -13) == 12
    @test DatesPlus.monthwrap(1, -12) == 1
    @test DatesPlus.monthwrap(1, -11) == 2
    @test DatesPlus.monthwrap(1, -10) == 3
    @test DatesPlus.monthwrap(1, -9) == 4
    @test DatesPlus.monthwrap(1, -8) == 5
    @test DatesPlus.monthwrap(1, -7) == 6
    @test DatesPlus.monthwrap(1, -6) == 7
    @test DatesPlus.monthwrap(1, -5) == 8
    @test DatesPlus.monthwrap(1, -4) == 9
    @test DatesPlus.monthwrap(1, -3) == 10
    @test DatesPlus.monthwrap(1, -2) == 11
    @test DatesPlus.monthwrap(1, -1) == 12
    @test DatesPlus.monthwrap(1, 0) == 1
    @test DatesPlus.monthwrap(1, 1) == 2
    @test DatesPlus.monthwrap(1, 2) == 3
    @test DatesPlus.monthwrap(1, 3) == 4
    @test DatesPlus.monthwrap(1, 4) == 5
    @test DatesPlus.monthwrap(1, 5) == 6
    @test DatesPlus.monthwrap(1, 6) == 7
    @test DatesPlus.monthwrap(1, 7) == 8
    @test DatesPlus.monthwrap(1, 8) == 9
    @test DatesPlus.monthwrap(1, 9) == 10
    @test DatesPlus.monthwrap(1, 10) == 11
    @test DatesPlus.monthwrap(1, 11) == 12
    @test DatesPlus.monthwrap(1, 12) == 1
    @test DatesPlus.monthwrap(1, 13) == 2
    @test DatesPlus.monthwrap(1, 24) == 1
    @test DatesPlus.monthwrap(12, -14) == 10
    @test DatesPlus.monthwrap(12, -13) == 11
    @test DatesPlus.monthwrap(12, -12) == 12
    @test DatesPlus.monthwrap(12, -11) == 1
    @test DatesPlus.monthwrap(12, -2) == 10
    @test DatesPlus.monthwrap(12, -1) == 11
    @test DatesPlus.monthwrap(12, 0) == 12
    @test DatesPlus.monthwrap(12, 1) == 1
    @test DatesPlus.monthwrap(12, 2) == 2
    @test DatesPlus.monthwrap(12, 11) == 11
    @test DatesPlus.monthwrap(12, 12) == 12
    @test DatesPlus.monthwrap(12, 13) == 1
end
@testset "Wrapping arithmetic for years" begin
    # yearwrap figures out the resulting year
    # when adding/subtracting months from a date
    @test DatesPlus.yearwrap(2000, 1, -3600) == 1700
    @test DatesPlus.yearwrap(2000, 1, -37) == 1996
    @test DatesPlus.yearwrap(2000, 1, -36) == 1997
    @test DatesPlus.yearwrap(2000, 1, -35) == 1997
    @test DatesPlus.yearwrap(2000, 1, -25) == 1997
    @test DatesPlus.yearwrap(2000, 1, -24) == 1998
    @test DatesPlus.yearwrap(2000, 1, -23) == 1998
    @test DatesPlus.yearwrap(2000, 1, -14) == 1998
    @test DatesPlus.yearwrap(2000, 1, -13) == 1998
    @test DatesPlus.yearwrap(2000, 1, -12) == 1999
    @test DatesPlus.yearwrap(2000, 1, -11) == 1999
    @test DatesPlus.yearwrap(2000, 1, -2) == 1999
    @test DatesPlus.yearwrap(2000, 1, -1) == 1999
    @test DatesPlus.yearwrap(2000, 1, 0) == 2000
    @test DatesPlus.yearwrap(2000, 1, 1) == 2000
    @test DatesPlus.yearwrap(2000, 1, 11) == 2000
    @test DatesPlus.yearwrap(2000, 1, 12) == 2001
    @test DatesPlus.yearwrap(2000, 1, 13) == 2001
    @test DatesPlus.yearwrap(2000, 1, 23) == 2001
    @test DatesPlus.yearwrap(2000, 1, 24) == 2002
    @test DatesPlus.yearwrap(2000, 1, 25) == 2002
    @test DatesPlus.yearwrap(2000, 1, 36) == 2003
    @test DatesPlus.yearwrap(2000, 1, 3600) == 2300
    @test DatesPlus.yearwrap(2000, 2, -2) == 1999
    @test DatesPlus.yearwrap(2000, 3, 10) == 2001
    @test DatesPlus.yearwrap(2000, 4, -4) == 1999
    @test DatesPlus.yearwrap(2000, 5, 8) == 2001
    @test DatesPlus.yearwrap(2000, 6, -18) == 1998
    @test DatesPlus.yearwrap(2000, 6, -6) == 1999
    @test DatesPlus.yearwrap(2000, 6, 6) == 2000
    @test DatesPlus.yearwrap(2000, 6, 7) == 2001
    @test DatesPlus.yearwrap(2000, 6, 19) == 2002
    @test DatesPlus.yearwrap(2000, 12, -3600) == 1700
    @test DatesPlus.yearwrap(2000, 12, -36) == 1997
    @test DatesPlus.yearwrap(2000, 12, -35) == 1998
    @test DatesPlus.yearwrap(2000, 12, -24) == 1998
    @test DatesPlus.yearwrap(2000, 12, -23) == 1999
    @test DatesPlus.yearwrap(2000, 12, -14) == 1999
    @test DatesPlus.yearwrap(2000, 12, -13) == 1999
    @test DatesPlus.yearwrap(2000, 12, -12) == 1999
    @test DatesPlus.yearwrap(2000, 12, -11) == 2000
    @test DatesPlus.yearwrap(2000, 12, -2) == 2000
    @test DatesPlus.yearwrap(2000, 12, -1) == 2000
    @test DatesPlus.yearwrap(2000, 12, 0) == 2000
    @test DatesPlus.yearwrap(2000, 12, 1) == 2001
    @test DatesPlus.yearwrap(2000, 12, 11) == 2001
    @test DatesPlus.yearwrap(2000, 12, 12) == 2001
    @test DatesPlus.yearwrap(2000, 12, 13) == 2002
    @test DatesPlus.yearwrap(2000, 12, 24) == 2002
    @test DatesPlus.yearwrap(2000, 12, 25) == 2003
    @test DatesPlus.yearwrap(2000, 12, 36) == 2003
    @test DatesPlus.yearwrap(2000, 12, 37) == 2004
    @test DatesPlus.yearwrap(2000, 12, 3600) == 2300
end
@testset "DateTime arithmetic" begin
    # DatesPlus.DateTime arithmetic
    a = DatesPlus.DateTime(2013, 1, 1, 0, 0, 0, 1)
    b = DatesPlus.DateTime(2013, 1, 1, 0, 0, 0, 0)
    @test a - b == DatesPlus.Millisecond(1)
    @test DatesPlus.DateTime(2013, 1, 2) - b == DatesPlus.Millisecond(86400000)

    @testset "DatesPlus.DateTime-Year arithmetic" begin
        dt = DatesPlus.DateTime(1999, 12, 27)
        @test dt + DatesPlus.Year(1) == DatesPlus.DateTime(2000, 12, 27)
        @test dt + DatesPlus.Year(100) == DatesPlus.DateTime(2099, 12, 27)
        @test dt + DatesPlus.Year(1000) == DatesPlus.DateTime(2999, 12, 27)
        @test dt - DatesPlus.Year(1) == DatesPlus.DateTime(1998, 12, 27)
        @test dt - DatesPlus.Year(100) == DatesPlus.DateTime(1899, 12, 27)
        @test dt - DatesPlus.Year(1000) == DatesPlus.DateTime(999, 12, 27)
        dt = DatesPlus.DateTime(2000, 2, 29)
        @test dt + DatesPlus.Year(1) == DatesPlus.DateTime(2001, 2, 28)
        @test dt - DatesPlus.Year(1) == DatesPlus.DateTime(1999, 2, 28)
        @test dt + DatesPlus.Year(4) == DatesPlus.DateTime(2004, 2, 29)
        @test dt - DatesPlus.Year(4) == DatesPlus.DateTime(1996, 2, 29)
        dt = DatesPlus.DateTime(1972, 6, 30, 23, 59, 59)
        @test dt + DatesPlus.Year(1) == DatesPlus.DateTime(1973, 6, 30, 23, 59, 59)
        @test dt - DatesPlus.Year(1) == DatesPlus.DateTime(1971, 6, 30, 23, 59, 59)
        @test dt + DatesPlus.Year(-1) == DatesPlus.DateTime(1971, 6, 30, 23, 59, 59)
        @test dt - DatesPlus.Year(-1) == DatesPlus.DateTime(1973, 6, 30, 23, 59, 59)

        dt = DatesPlus.DateTime(2000, 1, 1, 12, 30, 45, 500)
        dt2 = dt + DatesPlus.Year(1)
        @test DatesPlus.year(dt2) == 2001
        @test DatesPlus.month(dt2) == 1
        @test DatesPlus.day(dt2) == 1
        @test DatesPlus.hour(dt2) == 12
        @test DatesPlus.minute(dt2) == 30
        @test DatesPlus.second(dt2) == 45
        @test DatesPlus.millisecond(dt2) == 500
    end
    @testset "DateTime-Quarter arithmetic" begin
        dt = DatesPlus.DateTime(1999, 12, 27)
        @test dt + DatesPlus.Quarter(1) == DatesPlus.DateTime(2000, 3, 27)
        @test dt + DatesPlus.Quarter(-1) == DatesPlus.DateTime(1999, 9, 27)
    end

    @testset "DateTime-Month arithmetic" begin
        dt = DatesPlus.DateTime(1999, 12, 27)
        @test dt + DatesPlus.Month(1) == DatesPlus.DateTime(2000, 1, 27)
        @test dt + DatesPlus.Month(-1) == DatesPlus.DateTime(1999, 11, 27)
        @test dt + DatesPlus.Month(-11) == DatesPlus.DateTime(1999, 1, 27)
        @test dt + DatesPlus.Month(11) == DatesPlus.DateTime(2000, 11, 27)
        @test dt + DatesPlus.Month(-12) == DatesPlus.DateTime(1998, 12, 27)
        @test dt + DatesPlus.Month(12) == DatesPlus.DateTime(2000, 12, 27)
        @test dt + DatesPlus.Month(13) == DatesPlus.DateTime(2001, 1, 27)
        @test dt + DatesPlus.Month(100) == DatesPlus.DateTime(2008, 4, 27)
        @test dt + DatesPlus.Month(1000) == DatesPlus.DateTime(2083, 4, 27)
        @test dt - DatesPlus.Month(1) == DatesPlus.DateTime(1999, 11, 27)
        @test dt - DatesPlus.Month(-1) == DatesPlus.DateTime(2000, 1, 27)
        @test dt - DatesPlus.Month(100) == DatesPlus.DateTime(1991, 8, 27)
        @test dt - DatesPlus.Month(1000) == DatesPlus.DateTime(1916, 8, 27)
        dt = DatesPlus.DateTime(2000, 2, 29)
        @test dt + DatesPlus.Month(1) == DatesPlus.DateTime(2000, 3, 29)
        @test dt - DatesPlus.Month(1) == DatesPlus.DateTime(2000, 1, 29)
        dt = DatesPlus.DateTime(1972, 6, 30, 23, 59, 59)
        @test dt + DatesPlus.Month(1) == DatesPlus.DateTime(1972, 7, 30, 23, 59, 59)
        @test dt - DatesPlus.Month(1) == DatesPlus.DateTime(1972, 5, 30, 23, 59, 59)
        @test dt + DatesPlus.Month(-1) == DatesPlus.DateTime(1972, 5, 30, 23, 59, 59)
    end
    @testset "DateTime-Week arithmetic" begin
        dt = DatesPlus.DateTime(1999, 12, 27)
        @test dt + DatesPlus.Week(1) == DatesPlus.DateTime(2000, 1, 3)
        @test dt + DatesPlus.Week(100) == DatesPlus.DateTime(2001, 11, 26)
        @test dt + DatesPlus.Week(1000) == DatesPlus.DateTime(2019, 2, 25)
        @test dt - DatesPlus.Week(1) == DatesPlus.DateTime(1999, 12, 20)
        @test dt - DatesPlus.Week(100) == DatesPlus.DateTime(1998, 1, 26)
        @test dt - DatesPlus.Week(1000) == DatesPlus.DateTime(1980, 10, 27)
        dt = DatesPlus.DateTime(2000, 2, 29)
        @test dt + DatesPlus.Week(1) == DatesPlus.DateTime(2000, 3, 7)
        @test dt - DatesPlus.Week(1) == DatesPlus.DateTime(2000, 2, 22)
        dt = DatesPlus.DateTime(1972, 6, 30, 23, 59, 59)
        @test dt + DatesPlus.Week(1) == DatesPlus.DateTime(1972, 7, 7, 23, 59, 59)
        @test dt - DatesPlus.Week(1) == DatesPlus.DateTime(1972, 6, 23, 23, 59, 59)
        @test dt + DatesPlus.Week(-1) == DatesPlus.DateTime(1972, 6, 23, 23, 59, 59)
    end
    @testset "DateTime-Day arithmetic" begin
        dt = DatesPlus.DateTime(1999, 12, 27)
        @test dt + DatesPlus.Day(1) == DatesPlus.DateTime(1999, 12, 28)
        @test dt + DatesPlus.Day(100) == DatesPlus.DateTime(2000, 4, 5)
        @test dt + DatesPlus.Day(1000) == DatesPlus.DateTime(2002, 9, 22)
        @test dt - DatesPlus.Day(1) == DatesPlus.DateTime(1999, 12, 26)
        @test dt - DatesPlus.Day(100) == DatesPlus.DateTime(1999, 9, 18)
        @test dt - DatesPlus.Day(1000) == DatesPlus.DateTime(1997, 4, 1)
        dt = DatesPlus.DateTime(1972, 6, 30, 23, 59, 59)
        @test dt + DatesPlus.Day(1) == DatesPlus.DateTime(1972, 7, 1, 23, 59, 59)
        @test dt - DatesPlus.Day(1) == DatesPlus.DateTime(1972, 6, 29, 23, 59, 59)
        @test dt + DatesPlus.Day(-1) == DatesPlus.DateTime(1972, 6, 29, 23, 59, 59)
    end
    @testset "DateTime-Hour arithmetic" begin
        dt = DatesPlus.DateTime(1999, 12, 27)
        @test dt + DatesPlus.Hour(1) == DatesPlus.DateTime(1999, 12, 27, 1)
        @test dt + DatesPlus.Hour(100) == DatesPlus.DateTime(1999, 12, 31, 4)
        @test dt + DatesPlus.Hour(1000) == DatesPlus.DateTime(2000, 2, 6, 16)
        @test dt - DatesPlus.Hour(1) == DatesPlus.DateTime(1999, 12, 26, 23)
        @test dt - DatesPlus.Hour(100) == DatesPlus.DateTime(1999, 12, 22, 20)
        @test dt - DatesPlus.Hour(1000) == DatesPlus.DateTime(1999, 11, 15, 8)
        dt = DatesPlus.DateTime(1972, 6, 30, 23, 59, 59)
        @test dt + DatesPlus.Hour(1) == DatesPlus.DateTime(1972, 7, 1, 0, 59, 59)
        @test dt - DatesPlus.Hour(1) == DatesPlus.DateTime(1972, 6, 30, 22, 59, 59)
        @test dt + DatesPlus.Hour(-1) == DatesPlus.DateTime(1972, 6, 30, 22, 59, 59)
    end
    @testset "DateTime-Minute arithmetic" begin
        dt = DatesPlus.DateTime(1999, 12, 27)
        @test dt + DatesPlus.Minute(1) == DatesPlus.DateTime(1999, 12, 27, 0, 1)
        @test dt + DatesPlus.Minute(100) == DatesPlus.DateTime(1999, 12, 27, 1, 40)
        @test dt + DatesPlus.Minute(1000) == DatesPlus.DateTime(1999, 12, 27, 16, 40)
        @test dt - DatesPlus.Minute(1) == DatesPlus.DateTime(1999, 12, 26, 23, 59)
        @test dt - DatesPlus.Minute(100) == DatesPlus.DateTime(1999, 12, 26, 22, 20)
        @test dt - DatesPlus.Minute(1000) == DatesPlus.DateTime(1999, 12, 26, 7, 20)
        dt = DatesPlus.DateTime(1972, 6, 30, 23, 59, 59)
        @test dt + DatesPlus.Minute(1) == DatesPlus.DateTime(1972, 7, 1, 0, 0, 59)
        @test dt - DatesPlus.Minute(1) == DatesPlus.DateTime(1972, 6, 30, 23, 58, 59)
        @test dt + DatesPlus.Minute(-1) == DatesPlus.DateTime(1972, 6, 30, 23, 58, 59)
    end
    @testset "DateTime-Second arithmetic" begin
        dt = DatesPlus.DateTime(1999, 12, 27)
        @test dt + DatesPlus.Second(1) == DatesPlus.DateTime(1999, 12, 27, 0, 0, 1)
        @test dt + DatesPlus.Second(100) == DatesPlus.DateTime(1999, 12, 27, 0, 1, 40)
        @test dt + DatesPlus.Second(1000) == DatesPlus.DateTime(1999, 12, 27, 0, 16, 40)
        @test dt - DatesPlus.Second(1) == DatesPlus.DateTime(1999, 12, 26, 23, 59, 59)
        @test dt - DatesPlus.Second(100) == DatesPlus.DateTime(1999, 12, 26, 23, 58, 20)
        @test dt - DatesPlus.Second(1000) == DatesPlus.DateTime(1999, 12, 26, 23, 43, 20)
    end
    @testset "DateTime-Millisecond arithmetic" begin
        dt = DatesPlus.DateTime(1999, 12, 27)
        @test dt + DatesPlus.Millisecond(1) == DatesPlus.DateTime(1999, 12, 27, 0, 0, 0, 1)
        @test dt + DatesPlus.Millisecond(100) == DatesPlus.DateTime(1999, 12, 27, 0, 0, 0, 100)
        @test dt + DatesPlus.Millisecond(1000) == DatesPlus.DateTime(1999, 12, 27, 0, 0, 1)
        @test dt - DatesPlus.Millisecond(1) == DatesPlus.DateTime(1999, 12, 26, 23, 59, 59, 999)
        @test dt - DatesPlus.Millisecond(100) == DatesPlus.DateTime(1999, 12, 26, 23, 59, 59, 900)
        @test dt - DatesPlus.Millisecond(1000) == DatesPlus.DateTime(1999, 12, 26, 23, 59, 59)
        dt = DatesPlus.DateTime(1972, 6, 30, 23, 59, 59)
        @test dt + DatesPlus.Millisecond(1) == DatesPlus.DateTime(1972, 6, 30, 23, 59, 59, 1)
        @test dt - DatesPlus.Millisecond(1) == DatesPlus.DateTime(1972, 6, 30, 23, 59, 58, 999)
        @test dt + DatesPlus.Millisecond(-1) == DatesPlus.DateTime(1972, 6, 30, 23, 59, 58, 999)
    end
end
@testset "Date arithmetic" begin
    @testset "Date-Year arithmetic" begin
        dt = DatesPlus.Date(1999, 12, 27)
        @test dt + DatesPlus.Year(1) == DatesPlus.Date(2000, 12, 27)
        @test dt + DatesPlus.Year(100) == DatesPlus.Date(2099, 12, 27)
        @test dt + DatesPlus.Year(1000) == DatesPlus.Date(2999, 12, 27)
        @test dt - DatesPlus.Year(1) == DatesPlus.Date(1998, 12, 27)
        @test dt - DatesPlus.Year(100) == DatesPlus.Date(1899, 12, 27)
        @test dt - DatesPlus.Year(1000) == DatesPlus.Date(999, 12, 27)
        dt = DatesPlus.Date(2000, 2, 29)
        @test dt + DatesPlus.Year(1) == DatesPlus.Date(2001, 2, 28)
        @test dt - DatesPlus.Year(1) == DatesPlus.Date(1999, 2, 28)
        @test dt + DatesPlus.Year(4) == DatesPlus.Date(2004, 2, 29)
        @test dt - DatesPlus.Year(4) == DatesPlus.Date(1996, 2, 29)
    end
    @testset "Date-Quarter arithmetic" begin
        dt = DatesPlus.Date(1999, 12, 27)
        @test dt + DatesPlus.Quarter(1) == DatesPlus.Date(2000, 3, 27)
        @test dt - DatesPlus.Quarter(1) == DatesPlus.Date(1999, 9, 27)
    end
    @testset "Date-Month arithmetic" begin
        dt = DatesPlus.Date(1999, 12, 27)
        @test dt + DatesPlus.Month(1) == DatesPlus.Date(2000, 1, 27)
        @test dt + DatesPlus.Month(100) == DatesPlus.Date(2008, 4, 27)
        @test dt + DatesPlus.Month(1000) == DatesPlus.Date(2083, 4, 27)
        @test dt - DatesPlus.Month(1) == DatesPlus.Date(1999, 11, 27)
        @test dt - DatesPlus.Month(100) == DatesPlus.Date(1991, 8, 27)
        @test dt - DatesPlus.Month(1000) == DatesPlus.Date(1916, 8, 27)
        dt = DatesPlus.Date(2000, 2, 29)
        @test dt + DatesPlus.Month(1) == DatesPlus.Date(2000, 3, 29)
        @test dt - DatesPlus.Month(1) == DatesPlus.Date(2000, 1, 29)
    end
    @testset "Date-Week arithmetic" begin
        dt = DatesPlus.Date(1999, 12, 27)
        @test dt + DatesPlus.Week(1) == DatesPlus.Date(2000, 1, 3)
        @test dt + DatesPlus.Week(100) == DatesPlus.Date(2001, 11, 26)
        @test dt + DatesPlus.Week(1000) == DatesPlus.Date(2019, 2, 25)
        @test dt - DatesPlus.Week(1) == DatesPlus.Date(1999, 12, 20)
        @test dt - DatesPlus.Week(100) == DatesPlus.Date(1998, 1, 26)
        @test dt - DatesPlus.Week(1000) == DatesPlus.Date(1980, 10, 27)
        dt = DatesPlus.Date(2000, 2, 29)
        @test dt + DatesPlus.Week(1) == DatesPlus.Date(2000, 3, 7)
        @test dt - DatesPlus.Week(1) == DatesPlus.Date(2000, 2, 22)
    end
    @testset "Date-Day arithmetic" begin
        dt = DatesPlus.Date(1999, 12, 27)
        @test dt + DatesPlus.Day(1) == DatesPlus.Date(1999, 12, 28)
        @test dt + DatesPlus.Day(100) == DatesPlus.Date(2000, 4, 5)
        @test dt + DatesPlus.Day(1000) == DatesPlus.Date(2002, 9, 22)
        @test dt - DatesPlus.Day(1) == DatesPlus.Date(1999, 12, 26)
        @test dt - DatesPlus.Day(100) == DatesPlus.Date(1999, 9, 18)
        @test dt - DatesPlus.Day(1000) == DatesPlus.Date(1997, 4, 1)
    end
    @testset "Date-Time arithmetic" begin
        dt = DatesPlus.Date(1999, 12, 27)
        @test dt + DatesPlus.Time(0, 0, 0) == DatesPlus.DateTime(1999, 12, 27, 0, 0, 0)
        @test dt + DatesPlus.Time(1, 0, 0) == DatesPlus.DateTime(1999, 12, 27, 1, 0, 0)
        @test dt + DatesPlus.Time(0, 1, 0) == DatesPlus.DateTime(1999, 12, 27, 0, 1, 0)
        @test dt + DatesPlus.Time(0, 0, 1) == DatesPlus.DateTime(1999, 12, 27, 0, 0, 1)
        @test DatesPlus.Time(0, 0, 1) + dt == DatesPlus.DateTime(1999, 12, 27, 0, 0, 1)

        t = DatesPlus.Time(0, 0, 0) + DatesPlus.Hour(24)
        @test dt + t == DatesPlus.DateTime(1999, 12, 27, 0, 0, 0)
    end
end
@testset "Time-TimePeriod arithmetic" begin
    t = DatesPlus.Time(0)
    @test t + DatesPlus.Hour(1) == DatesPlus.Time(1)
    @test t - DatesPlus.Hour(1) == DatesPlus.Time(23)
    @test t - DatesPlus.Nanosecond(1) == DatesPlus.Time(23, 59, 59, 999, 999, 999)
    @test t + DatesPlus.Nanosecond(-1) == DatesPlus.Time(23, 59, 59, 999, 999, 999)
    @test t + DatesPlus.Hour(24) == t
    @test t + DatesPlus.Nanosecond(86400000000000) == t
    @test t - DatesPlus.Nanosecond(86400000000000) == t
    @test t + DatesPlus.Minute(1) == DatesPlus.Time(0, 1)
    @test t + DatesPlus.Second(1) == DatesPlus.Time(0, 0, 1)
    @test t + DatesPlus.Millisecond(1) == DatesPlus.Time(0, 0, 0, 1)
    @test t + DatesPlus.Microsecond(1) == DatesPlus.Time(0, 0, 0, 0, 1)
    @test_throws MethodError t + DatesPlus.Day(1)
    @testset "Time-TimePeriod arithmetic inequalities" begin
        t = DatesPlus.Time(4, 20)
        @test t - DatesPlus.Nanosecond(1) < t
        @test t + DatesPlus.Nanosecond(1) > t
        @test t + DatesPlus.Hour(24) < typemax(DatesPlus.Time)
        @test t - DatesPlus.Hour(24) > typemin(DatesPlus.Time)
        @test t + DatesPlus.Hour(23) < t
        @test t + DatesPlus.Hour(25) > t
    end
end
@testset "Month arithmetic and non-associativity" begin
    # Month arithmetic minimizes "edit distance", or number of changes
    # needed to get a correct answer
    # This approach results in a few cases of non-associativity
    a = DatesPlus.Date(2012, 1, 29)
    @test (a + DatesPlus.Day(1)) + DatesPlus.Month(1) != (a + DatesPlus.Month(1)) + DatesPlus.Day(1)
    a = DatesPlus.Date(2012, 1, 30)
    @test (a + DatesPlus.Day(1)) + DatesPlus.Month(1) != (a + DatesPlus.Month(1)) + DatesPlus.Day(1)
    a = DatesPlus.Date(2012, 2, 29)
    @test (a + DatesPlus.Day(1)) + DatesPlus.Month(1) != (a + DatesPlus.Month(1)) + DatesPlus.Day(1)
    a = DatesPlus.Date(2012, 3, 30)
    @test (a + DatesPlus.Day(1)) + DatesPlus.Month(1) != (a + DatesPlus.Month(1)) + DatesPlus.Day(1)
    a = DatesPlus.Date(2012, 4, 30)
    @test (a + DatesPlus.Day(1)) + DatesPlus.Month(1) != (a + DatesPlus.Month(1)) + DatesPlus.Day(1)
    a = DatesPlus.Date(2012, 5, 30)
    @test (a + DatesPlus.Day(1)) + DatesPlus.Month(1) != (a + DatesPlus.Month(1)) + DatesPlus.Day(1)
    a = DatesPlus.Date(2012, 6, 30)
    @test (a + DatesPlus.Day(1)) + DatesPlus.Month(1) != (a + DatesPlus.Month(1)) + DatesPlus.Day(1)
    a = DatesPlus.Date(2012, 8, 30)
    @test (a + DatesPlus.Day(1)) + DatesPlus.Month(1) != (a + DatesPlus.Month(1)) + DatesPlus.Day(1)
    a = DatesPlus.Date(2012, 9, 30)
    @test (a + DatesPlus.Day(1)) + DatesPlus.Month(1) != (a + DatesPlus.Month(1)) + DatesPlus.Day(1)
    a = DatesPlus.Date(2012, 10, 30)
    @test (a + DatesPlus.Day(1)) + DatesPlus.Month(1) != (a + DatesPlus.Month(1)) + DatesPlus.Day(1)
    a = DatesPlus.Date(2012, 11, 30)
    @test (a + DatesPlus.Day(1)) + DatesPlus.Month(1) != (a + DatesPlus.Month(1)) + DatesPlus.Day(1)
end
@testset "Vectorized arithmetic" begin
    # Vectorized Time arithmetic
    a = DatesPlus.Time(1, 1, 1)
    dr = [a, a, a, a, a, a, a, a, a, a]
    b = a + DatesPlus.Hour(1)
    @test dr .+ DatesPlus.Hour(1) == repeat([b], 10)
    b = a + DatesPlus.Second(1)
    @test dr .+ DatesPlus.Second(1) == repeat([b], 10)
    b = a + DatesPlus.Millisecond(1)
    @test dr .+ DatesPlus.Millisecond(1) == repeat([b], 10)
    b = a + DatesPlus.Microsecond(1)
    @test dr .+ DatesPlus.Microsecond(1) == repeat([b], 10)
    b = a + DatesPlus.Nanosecond(1)
    @test dr .+ DatesPlus.Nanosecond(1) == repeat([b], 10)

    b = a - DatesPlus.Hour(1)
    @test dr .- DatesPlus.Hour(1) == repeat([b], 10)
    b = a - DatesPlus.Second(1)
    @test dr .- DatesPlus.Second(1) == repeat([b], 10)
    b = a - DatesPlus.Millisecond(1)
    @test dr .- DatesPlus.Millisecond(1) == repeat([b], 10)
    b = a - DatesPlus.Microsecond(1)
    @test dr .- DatesPlus.Microsecond(1) == repeat([b], 10)
    b = a - DatesPlus.Nanosecond(1)
    @test dr .- DatesPlus.Nanosecond(1) == repeat([b], 10)

    # Vectorized arithmetic
    a = DatesPlus.Date(2014, 1, 1)
    dr = [a, a, a, a, a, a, a, a, a, a]
    b = a + DatesPlus.Year(1)
    @test dr .+ DatesPlus.Year(1) == repeat([b], 10)
    b = a + DatesPlus.Month(1)
    @test dr .+ DatesPlus.Month(1) == repeat([b], 10)
    b = a + DatesPlus.Day(1)
    @test dr .+ DatesPlus.Day(1) == repeat([b], 10)
    b = a - DatesPlus.Year(1)
    @test dr .- DatesPlus.Year(1) == repeat([b], 10)
    b = a - DatesPlus.Month(1)
    @test dr .- DatesPlus.Month(1) == repeat([b], 10)
    b = a - DatesPlus.Day(1)
    @test dr .- DatesPlus.Day(1) == repeat([b], 10)

    # Vectorized arithmetic
    b = a + DatesPlus.Year(1)
    @test dr .+ DatesPlus.Year(1) == repeat([b], 10)
    b = a + DatesPlus.Month(1)
    @test dr .+ DatesPlus.Month(1) == repeat([b], 10)
    b = a + DatesPlus.Day(1)
    @test dr .+ DatesPlus.Day(1) == repeat([b], 10)
    b = a - DatesPlus.Year(1)
    @test dr .- DatesPlus.Year(1) == repeat([b], 10)
    b = a - DatesPlus.Month(1)
    @test dr .- DatesPlus.Month(1) == repeat([b], 10)
    b = a - DatesPlus.Day(1)
    @test dr .- DatesPlus.Day(1) == repeat([b], 10)
    t1 = [DatesPlus.Date(2009, 1, 1) DatesPlus.Date(2009, 1, 2) DatesPlus.Date(2009, 1, 3); DatesPlus.Date(2009, 2, 1) DatesPlus.Date(2009, 2, 2) DatesPlus.Date(2009, 2, 3)]
    t2 = [DatesPlus.Date(2009, 1, 2) DatesPlus.Date(2009, 2, 2) DatesPlus.Date(2010, 1, 3); DatesPlus.Date(2010, 2, 1) DatesPlus.Date(2009, 3, 2) DatesPlus.Date(2009, 2, 4)]
    t3 = [DatesPlus.DateTime(2009, 1, 1), DatesPlus.DateTime(2009, 1, 2), DatesPlus.DateTime(2009, 1, 3)]
    t4 = [DatesPlus.DateTime(2009, 1, 1, 0, 0, 1), DatesPlus.DateTime(2009, 1, 2, 0, 1), DatesPlus.DateTime(2009, 1, 3, 1)]
    t5 = [DatesPlus.Time(0, 0, 0) DatesPlus.Time(0, 0, 1) DatesPlus.Time(0, 0, 2); DatesPlus.Time(0, 1, 0) DatesPlus.Time(0, 2, 0) DatesPlus.Time(0, 3, 0)]

    @testset "TimeType, Array{TimeType}" begin
        @test DatesPlus.Date(2010, 1, 1) .- t1 == [DatesPlus.Day(365) DatesPlus.Day(364) DatesPlus.Day(363); DatesPlus.Day(334) DatesPlus.Day(333) DatesPlus.Day(332)]
        @test t1 .- DatesPlus.Date(2010, 1, 1) == [DatesPlus.Day(-365) DatesPlus.Day(-364) DatesPlus.Day(-363); DatesPlus.Day(-334) DatesPlus.Day(-333) DatesPlus.Day(-332)]
        @test DatesPlus.DateTime(2009, 1, 1) .- t3 == [DatesPlus.Millisecond(0), DatesPlus.Millisecond(-86400000), DatesPlus.Millisecond(-172800000)]
        @test t3 .- DatesPlus.DateTime(2009, 1, 1) == [DatesPlus.Millisecond(0), DatesPlus.Millisecond(86400000), DatesPlus.Millisecond(172800000)]
        @test DatesPlus.Time(2) .- t5 == [DatesPlus.Nanosecond(7200000000000) DatesPlus.Nanosecond(7199000000000) DatesPlus.Nanosecond(7198000000000);
                                      DatesPlus.Nanosecond(7140000000000) DatesPlus.Nanosecond(7080000000000) DatesPlus.Nanosecond(7020000000000)]
    end
    @testset "GeneralPeriod, Array{TimeType}" begin
        @test DatesPlus.Day(1) .+ t1 == [DatesPlus.Date(2009, 1, 2) DatesPlus.Date(2009, 1, 3) DatesPlus.Date(2009, 1, 4); DatesPlus.Date(2009, 2, 2) DatesPlus.Date(2009, 2, 3) DatesPlus.Date(2009, 2, 4)]
        @test DatesPlus.Hour(1) .+ t3 == [DatesPlus.DateTime(2009, 1, 1, 1), DatesPlus.DateTime(2009, 1, 2, 1), DatesPlus.DateTime(2009, 1, 3, 1)]
        @test t1 .+ DatesPlus.Day(1) == [DatesPlus.Date(2009, 1, 2) DatesPlus.Date(2009, 1, 3) DatesPlus.Date(2009, 1, 4); DatesPlus.Date(2009, 2, 2) DatesPlus.Date(2009, 2, 3) DatesPlus.Date(2009, 2, 4)]
        @test t3 .+ DatesPlus.Hour(1) == [DatesPlus.DateTime(2009, 1, 1, 1), DatesPlus.DateTime(2009, 1, 2, 1), DatesPlus.DateTime(2009, 1, 3, 1)]

        @test (DatesPlus.Month(1) + DatesPlus.Day(1)) .+ t1 == [DatesPlus.Date(2009, 2, 2) DatesPlus.Date(2009, 2, 3) DatesPlus.Date(2009, 2, 4); DatesPlus.Date(2009, 3, 2) DatesPlus.Date(2009, 3, 3) DatesPlus.Date(2009, 3, 4)]
        @test (DatesPlus.Hour(1) + DatesPlus.Minute(1)) .+ t3 == [DatesPlus.DateTime(2009, 1, 1, 1, 1), DatesPlus.DateTime(2009, 1, 2, 1, 1), DatesPlus.DateTime(2009, 1, 3, 1, 1)]
        @test t1 .+ (DatesPlus.Month(1) + DatesPlus.Day(1)) == [DatesPlus.Date(2009, 2, 2) DatesPlus.Date(2009, 2, 3) DatesPlus.Date(2009, 2, 4); DatesPlus.Date(2009, 3, 2) DatesPlus.Date(2009, 3, 3) DatesPlus.Date(2009, 3, 4)]
        @test t3 .+ (DatesPlus.Hour(1) + DatesPlus.Minute(1)) == [DatesPlus.DateTime(2009, 1, 1, 1, 1), DatesPlus.DateTime(2009, 1, 2, 1, 1), DatesPlus.DateTime(2009, 1, 3, 1, 1)]

        @test t1 .- DatesPlus.Day(1) == [DatesPlus.Date(2008, 12, 31) DatesPlus.Date(2009, 1, 1) DatesPlus.Date(2009, 1, 2); DatesPlus.Date(2009, 1, 31) DatesPlus.Date(2009, 2, 1) DatesPlus.Date(2009, 2, 2)]
        @test t3 .- DatesPlus.Hour(1) == [DatesPlus.DateTime(2008, 12, 31, 23), DatesPlus.DateTime(2009, 1, 1, 23), DatesPlus.DateTime(2009, 1, 2, 23)]

        @test t1 .- (DatesPlus.Month(1) + DatesPlus.Day(1)) == [DatesPlus.Date(2008, 11, 30) DatesPlus.Date(2008, 12, 1) DatesPlus.Date(2008, 12, 2); DatesPlus.Date(2008, 12, 31) DatesPlus.Date(2009, 1, 1) DatesPlus.Date(2009, 1, 2)]
        @test t3 .- (DatesPlus.Hour(1) + DatesPlus.Minute(1)) == [DatesPlus.DateTime(2008, 12, 31, 22, 59), DatesPlus.DateTime(2009, 1, 1, 22, 59), DatesPlus.DateTime(2009, 1, 2, 22, 59)]
    end
    @testset "#20205" begin
        # ensure commutative subtraction methods are not defined
        @test_throws MethodError DatesPlus.Day(1) .- t1
        @test_throws MethodError DatesPlus.Hour(1) .- t3
        @test_throws MethodError (DatesPlus.Month(1) + DatesPlus.Day(1)) .- t1
        @test_throws MethodError (DatesPlus.Hour(1) + DatesPlus.Minute(1)) .- t3
    end
    @testset "deprecated" begin
        @test DatesPlus.Date(2010, 1, 1) - t1 == [DatesPlus.Day(365) DatesPlus.Day(364) DatesPlus.Day(363); DatesPlus.Day(334) DatesPlus.Day(333) DatesPlus.Day(332)]
        @test t1 - DatesPlus.Date(2010, 1, 1) == [DatesPlus.Day(-365) DatesPlus.Day(-364) DatesPlus.Day(-363); DatesPlus.Day(-334) DatesPlus.Day(-333) DatesPlus.Day(-332)]
        @test DatesPlus.DateTime(2009, 1, 1) - t3 == [DatesPlus.Millisecond(0), DatesPlus.Millisecond(-86400000), DatesPlus.Millisecond(-172800000)]
        @test t3 - DatesPlus.DateTime(2009, 1, 1) == [DatesPlus.Millisecond(0), DatesPlus.Millisecond(86400000), DatesPlus.Millisecond(172800000)]
        @test DatesPlus.Day(1) + t1 == [DatesPlus.Date(2009, 1, 2) DatesPlus.Date(2009, 1, 3) DatesPlus.Date(2009, 1, 4); DatesPlus.Date(2009, 2, 2) DatesPlus.Date(2009, 2, 3) DatesPlus.Date(2009, 2, 4)]
        @test DatesPlus.Hour(1) + t3 == [DatesPlus.DateTime(2009, 1, 1, 1), DatesPlus.DateTime(2009, 1, 2, 1), DatesPlus.DateTime(2009, 1, 3, 1)]
        @test t1 + DatesPlus.Day(1) == [DatesPlus.Date(2009, 1, 2) DatesPlus.Date(2009, 1, 3) DatesPlus.Date(2009, 1, 4); DatesPlus.Date(2009, 2, 2) DatesPlus.Date(2009, 2, 3) DatesPlus.Date(2009, 2, 4)]
        @test t3 + DatesPlus.Hour(1) == [DatesPlus.DateTime(2009, 1, 1, 1), DatesPlus.DateTime(2009, 1, 2, 1), DatesPlus.DateTime(2009, 1, 3, 1)]
        @test t5 + DatesPlus.Hour(1) == [DatesPlus.Time(1, 0, 0) DatesPlus.Time(1, 0, 1) DatesPlus.Time(1, 0, 2); DatesPlus.Time(1, 1, 0) DatesPlus.Time(1, 2, 0) DatesPlus.Time(1, 3, 0)]
        @test (DatesPlus.Month(1) + DatesPlus.Day(1)) + t1 == [DatesPlus.Date(2009, 2, 2) DatesPlus.Date(2009, 2, 3) DatesPlus.Date(2009, 2, 4); DatesPlus.Date(2009, 3, 2) DatesPlus.Date(2009, 3, 3) DatesPlus.Date(2009, 3, 4)]
        @test (DatesPlus.Hour(1) + DatesPlus.Minute(1)) + t3 == [DatesPlus.DateTime(2009, 1, 1, 1, 1), DatesPlus.DateTime(2009, 1, 2, 1, 1), DatesPlus.DateTime(2009, 1, 3, 1, 1)]
        @test t1 + (DatesPlus.Month(1) + DatesPlus.Day(1)) == [DatesPlus.Date(2009, 2, 2) DatesPlus.Date(2009, 2, 3) DatesPlus.Date(2009, 2, 4); DatesPlus.Date(2009, 3, 2) DatesPlus.Date(2009, 3, 3) DatesPlus.Date(2009, 3, 4)]
        @test t3 + (DatesPlus.Hour(1) + DatesPlus.Minute(1)) == [DatesPlus.DateTime(2009, 1, 1, 1, 1), DatesPlus.DateTime(2009, 1, 2, 1, 1), DatesPlus.DateTime(2009, 1, 3, 1, 1)]
        @test t1 - DatesPlus.Day(1) == [DatesPlus.Date(2008, 12, 31) DatesPlus.Date(2009, 1, 1) DatesPlus.Date(2009, 1, 2); DatesPlus.Date(2009, 1, 31) DatesPlus.Date(2009, 2, 1) DatesPlus.Date(2009, 2, 2)]
        @test t3 - DatesPlus.Hour(1) == [DatesPlus.DateTime(2008, 12, 31, 23), DatesPlus.DateTime(2009, 1, 1, 23), DatesPlus.DateTime(2009, 1, 2, 23)]
        @test t1 - (DatesPlus.Month(1) + DatesPlus.Day(1)) == [DatesPlus.Date(2008, 11, 30) DatesPlus.Date(2008, 12, 1) DatesPlus.Date(2008, 12, 2); DatesPlus.Date(2008, 12, 31) DatesPlus.Date(2009, 1, 1) DatesPlus.Date(2009, 1, 2)]
        @test t3 - (DatesPlus.Hour(1) + DatesPlus.Minute(1)) == [DatesPlus.DateTime(2008, 12, 31, 22, 59), DatesPlus.DateTime(2009, 1, 1, 22, 59), DatesPlus.DateTime(2009, 1, 2, 22, 59)]
        @test t2 - t1 == [DatesPlus.Day(1) DatesPlus.Day(31) DatesPlus.Day(365); DatesPlus.Day(365) DatesPlus.Day(28) DatesPlus.Day(1)]
        @test t4 - t3 == [DatesPlus.Millisecond(1000), DatesPlus.Millisecond(60000), DatesPlus.Millisecond(3600000)]
        @test (DatesPlus.Date(2009, 1, 1):DatesPlus.Week(1):DatesPlus.Date(2009, 1, 21)) - (DatesPlus.Date(2009, 1, 1):DatesPlus.Day(1):DatesPlus.Date(2009, 1, 3)) == [DatesPlus.Day(0), DatesPlus.Day(6), DatesPlus.Day(12)]
        @test (DatesPlus.DateTime(2009, 1, 1, 1, 1, 1):DatesPlus.Second(1):DatesPlus.DateTime(2009, 1, 1, 1, 1, 3)) - (DatesPlus.DateTime(2009, 1, 1, 1, 1):DatesPlus.Second(1):DatesPlus.DateTime(2009, 1, 1, 1, 1, 2)) == [DatesPlus.Second(1), DatesPlus.Second(1), DatesPlus.Second(1)]
        @test_throws MethodError DatesPlus.Day(1) - t1
        @test_throws MethodError DatesPlus.Hour(1) - t3
        @test_throws MethodError (DatesPlus.Month(1) + DatesPlus.Day(1)) - t1
        @test_throws MethodError (DatesPlus.Hour(1) + DatesPlus.Minute(1)) - t3
    end
    @testset "TimeZone" begin
        # best we can get in Dates as there is no other tz functionality
        @test ((a, b) -> now(typeof(a))).(UTC(), [1,2,3]) isa Vector{DateTime}
    end
end

@testset "Missing arithmetic" begin
    for t ∈ [Date, Time, Day, Month, Week, Year, Hour, Microsecond, Millisecond, Minute, Nanosecond, Second]
        @test ismissing(t(1) + missing)
        @test ismissing(missing + t(1))
        @test ismissing(t(1) - missing)
        @test ismissing(missing - t(1))
    end
end

@testset "Diff of dates" begin
    for t ∈ [Day, Week, Hour, Minute]
        a = DateTime(2021,1,1):t(1):DateTime(2021,2,1)
        d = diff(a)
        @test d == diff(collect(a))
        @test eltype(d) === typeof(a[1] - a[2])
    end
end

end


using Statistics
using Random
using Distributions
using StaticArrays
using JSON
using POMDPs
using MCTS
using Logging
using JLD2
using POMDPs
using POMDPModels
using POMDPPolicies

using SCoBA
using SCoBA.SCoBADomains
using SCoBA.SCoBADomains: LatLonCoords
using SCoBA.SCoBASolver

# For warnings about exceeding conflict thresholds
global_logger(SimpleLogger(stderr, Logging.Warn))

# Hardcode the depot locations to get good coverage
const DEPOT1 = LatLonCoords((lat = 37.774524, lon = -122.473656))
const DEPOT2 = LatLonCoords((lat = 37.751751, lon = -122.410654))
const DEPOT3 = LatLonCoords((lat = 37.718779, lon = -122.462401))
const DEPOT4 = LatLonCoords((lat = 37.789290, lon = -122.426797))
const DEPOT5 = LatLonCoords((lat = 37.739611, lon = -122.492203))
const DEPOTLOCS = [DEPOT1, DEPOT2, DEPOT3, DEPOT4, DEPOT5]
const TRAVELTIME_EST = "./param_files/sf_halton_tt_estimates_scoba.jld2"
const PARAMS_FN = "./param_files/sf_bb_params.toml"
const rng = MersenneTwister(1345)
const mcts_params = (trials=100, explore=0.1, depth=20)

function main()

    # Get command line input from script
    parsed_args = parse_routing_commandline()

    # Set up bookkeeping datastructures for stats
    trials = parsed_args["trials"]
    results_dict = Dict()
    results_dict["trials"] = trials

    late_pkgs = Int64[]
    delivered_pkgs = Int64[]
    total_pkgs = Int64[]

    # Print the filename for tracking from the high-level bash script
    println(parsed_args["out_file_name"])

    @load TRAVELTIME_EST scoba_halton_tree travel_time_estimates

    # Define set of drones
    drones_per_depot = div(parsed_args["n_drones"], parsed_args["n_depots"])
    drone_ordering = String[]
    drone_set = Dict{String,Drone}()

    n = 1
    for nd = 1:parsed_args["n_depots"]
        for dd = 1:drones_per_depot

            drone_nm = string("dn", n)
            n += 1
            new_drone = Drone(depot_number=nd, depot_loc=DEPOTLOCS[nd])

            push!(drone_ordering, drone_nm)
            drone_set[drone_nm] = new_drone
        end
    end # for in n_depots

    # NOTE: Set number of initial requests slightly higher than drones
    num_init_requests = convert(Int64, round(1.5 * parsed_args["n_drones"]))

    if parsed_args["baseline"] == "mcts" # Special things for MDP

        for i = 1:trials

            println("Trial $(i)")

            # Reset drone prop set
            drone_prop_set = Dict{String,DroneProperties}()
            for drone_nm in drone_ordering
                drone_prop_set[drone_nm] = DroneProperties()
            end

            drone_server = RoutingAllocation(agent_set=drone_set, agent_prop_set=drone_prop_set,
                                             agent_ordering=drone_ordering,
                                             max_tasks_to_consider=20,
                                             conflict_threshold=10)

            # Reset sim
            sim = setup_routing_sim(PARAMS_FN, scoba_halton_tree, travel_time_estimates,
                                    num_init_requests=num_init_requests,
                                    new_request_prob=parsed_args["new_request_prob"],
                                    time_window_duration=parsed_args["time_window"],
                                    rng=rng)

            # Setup MDP and MCTS solver
            mdp = RoutingMCTSMDP(sim, drone_server, parsed_args["timesteps"], Int64[], Tuple{Float64,Float64}[])
            nearestpol = NearestPkgPolicy(mdp)

            mcts = MCTSSolver(n_iterations=mcts_params.trials, depth=mcts_params.depth, rng=rng,
                              exploration_constant=mcts_params.explore,
                              reuse_tree=false,
                              estimate_value = RolloutEstimator(nearestpol))
            mcts_policy = POMDPs.solve(mcts, mdp)

            for t = 1:parsed_args["timesteps"]

                update_time_windows!(sim, drone_server)
                update_routing_mcts_fullstate!(mdp, rng)

                # Get joint action from individual agent concatenation
                curr_action = Int64[]
                for idx = 1:parsed_args["n_drones"]

                    drone_state = get_current_routing_mcts_state(mdp, idx)
                    drone_action = POMDPs.action(mcts_policy, drone_state)
                    mdp.drone_pkg_assignment[idx] = drone_action
                    push!(curr_action, drone_action)
                end

                update_server_with_mdp_action!(mdp, curr_action, rng)
                update_routing_sim!(mdp.routing_sim, mdp.server, rng)

            end

            push!(late_pkgs, sim.late_packages)
            push!(delivered_pkgs, sim.delivered_packages)
            push!(total_pkgs, sim.num_total_packages)
        end # for i = 1:trials

    else
        # One of the non-MDP methods
        if parsed_args["baseline"] == "edd"
            baseline_fn = earliest_due_date!
        elseif parsed_args["baseline"] == "hungarian"
            baseline_fn = expected_hungarian!
        elseif parsed_args["baseline"] == "scoba"
            baseline_fn = scoba_routing!
        else
            throw(ErrorException("Incorrect baseline argument $(parsed_args["baseline"])!"))
        end

        for i = 1:trials

            println("Trial $(i)")

            # Reset drone prop set
            drone_prop_set = Dict{String,DroneProperties}()
            for drone_nm in drone_ordering
                drone_prop_set[drone_nm] = DroneProperties()
            end

            drone_server = RoutingAllocation(agent_set=drone_set, agent_prop_set=drone_prop_set, agent_ordering=drone_ordering)

            # Reset sim
            sim = setup_routing_sim(PARAMS_FN, scoba_halton_tree, travel_time_estimates,
                                    num_init_requests=num_init_requests,
                                    new_request_prob=parsed_args["new_request_prob"],
                                    time_window_duration=parsed_args["time_window"],
                                    rng=rng)

            for t = 1:parsed_args["timesteps"]
                update_time_windows!(sim, drone_server)
                baseline_fn(drone_server, sim, rng)
                update_routing_sim!(sim, drone_server, rng)
            end

            push!(late_pkgs, sim.late_packages)
            push!(delivered_pkgs, sim.delivered_packages)
            push!(total_pkgs, sim.num_total_packages)
        end # for i = 1:trials
    end # Outer if

    results_dict["late"] = late_pkgs
    results_dict["delivered"] = delivered_pkgs
    results_dict["total"] = total_pkgs

    # Print to output file
    open(parsed_args["out_file_name"], "w") do f
        JSON.print(f, results_dict, 2)
    end
end # main

main()

# Example call to routing script
# You can also run julia1.4 scripts/benchmark_routing.jl --help to get details of the command line params
# julia1.4 scripts/benchmark_routing.jl -n 10 -t 100 -d 15 -e 5 -p 0.5 -w 30 scoba r_dr15_dep5_probpt5_win30_scoba.json


config_file = isempty(ARGS) ? joinpath(@__DIR__, "config", "config_precompute_parameters.jl") : ARGS[1]
isfile(config_file) || error("Cannot read '$config_file'")
println("Config supplied: '$config_file'")
include(config_file)

using SVDD, OneClassActiveLearning, Distributed, Gurobi, JuMP, MLKernels
using Serialization

include(joinpath(@__DIR__, "util", "setup_workers.jl"))

@info "Loading packages on all workers."
@everywhere using SVDD, OneClassActiveLearning, OneClassSampling, Gurobi, JuMP, Memento

@everywhere function Memento.debug(logger::Logger, error::ErrorException)
    Memento.warn(logger, "Caught ErrorException, msg='$(error.msg)')")
end

@info "Searching for data sets in '$(data_input_root)'"
data_files = Vector{String}()
for data_set in vcat(values(data_dirs)...)
    data_dir = joinpath(data_input_root, data_set)
    if !isdir(data_dir)
        @info "Could not find dataset $data_dir"
        continue
    end
    local_names = readdir(data_dir)
    full_names = realpath.(joinpath.(data_input_root, data_dir, local_names))
    push!(data_files, full_names...)
end

@info "Initializing models."
all_model_params = @distributed vcat for file in data_files
    data, labels = load_data(file)
    n_obs = length(labels)
    params = Dict{Symbol, Any}(
        :file => file,
        :n_observations => n_obs
    )
    # SVDD
    model = instantiate(VanillaSVDD, data, fill(:U, n_obs), Dict{Symbol, Any}())
    initialize!(model, init_strategy[:svdd])
    model_parameters = get_model_params(model)
    params[:svdd] = Dict{Symbol, Any}(
        :type => Symbol(typeof(model)),
        :C => get_model_params(model)[:C],
        :gamma => MLKernels.getvalue(model.kernel_fct.alpha)
    )
    # PWC
    pwc_gamma = init_strategy[:pwc][:gamma](data)
    pwc_threshold = calculate_threshold(init_strategy[:pwc][:threshold_strat], data, labels, pwc_gamma)
    params[:pwc] = Dict{Symbol, Any}(
        :gamma => pwc_gamma,
        :threshold => pwc_threshold,
        :threshold_strat => Symbol(typeof(init_strategy[:pwc][:threshold_strat]))
    )
    params
end

@info "Saving parameters."
model_params_by_filename = Dict{String, Dict{Symbol, Any}}()
for model_params in all_model_params
    model_params_by_filename[basename(model_params[:file])] = model_params
    delete!(model_params, :file)
end

serialize(data_parameter_file, model_params_by_filename)
@info "Done."


"""
    mutable struct Network <: ANetwork
        ne::Int
        edge_index_range::Int
        out_edges::Vector{Vector{Pair{Int,Int}}}  #unordered adjlist
        epos::Vector{Pair{Int,Int}}    # position of the edge in out_edges
        free_indexes::Vector{Int}       # indexes of deleted edges to be used up
                                        # for new edges to avoid very large
                                        # indexes, and unnecessary property map
                                        # memory used
        props::PropertyStore
    end

A type representing a directed graph with indexed edges.

    Network(n=0)

Construct a `Network` with `n` vertices and no edges.

    Network(adjmx::AbstractMatrix; selfedges=true, upper=false)

Construct a `Network` from the adjacency matrix `adjmx`, placing an edge in
correspondence to each nonzero element of `adjmx`.
If `selfedges=false` the diagonal elements of `adjmx` are ignored.
If `upper=true` only the upper triangular part of `adjmx` is considered.
"""
mutable struct Network <: ANetwork
    ne::Int
    edge_index_range::Int
    out_edges::Vector{Vector{Pair{Int,Int}}}  #unordered adjlist
    epos::Vector{Pair{Int,Int}}    # position of the edge in out_edges
    free_indexes::Vector{Int}       # indexes of deleted edges to be used up
                                    # for new edges to avoid very large
                                    # indexes, and unnecessary property map
                                    # memory use

    props::PropertyStore
end

"""
    mutable struct DiNetwork <: ADiNetwork
        ne::Int
        edge_index_range::Int
        out_edges::Vector{Vector{Pair{Int,Int}}}  #unordered out_adjlist
        in_edges::Vector{Vector{Pair{Int,Int}}}  #unordered in_adjlist

        epos::Vector{Pair{Int,Int}}    # position of the edge in out_edges
                                        # the first in the pair is the vertex
                                        # with lower index

        free_indexes::Vector{Int}       # indexes of deleted edges to be used up
                                        # for new edges to avoid very large
                                        # indexes, and unnecessary property map
                                        # memory use
        props::PropertyStore
    end

A type representing an directed graph with indexed edges.

    DiNetwork(n=0)

Construct a `DiNetwork` with `n` vertices and no edges.

    DiNetwork(adjmx::AbstractMatrix; selfedges=true)

Construct a `DiNetwork` from the adjacency matrix `adjmx`.
If `selfedges=false` the diagonal elements of `adjmx` are ignored.
"""
mutable struct DiNetwork <: ADiNetwork
    ne::Int
    edge_index_range::Int
    out_edges::Vector{Vector{Pair{Int,Int}}}  #unordered out_adjlist
    in_edges::Vector{Vector{Pair{Int,Int}}}  #unordered in_adjlist
    epos::Vector{Pair{Int,Int}}    # position of the edge in out_edges
    free_indexes::Vector{Int}       # indexes of deleted edges to be used up
                                    # for new edges to avoid very large
                                    # indexes, and unnecessary property map
                                    # memory use

    props::PropertyStore
end

const NetOrDiNet = Union{Network, DiNetwork}

edgetype(::Type{G}) where {G<:NetOrDiNet} = IndexedEdge
graphtype(::Type{DiNetwork}) = Network
digraphtype(::Type{Network}) = DiNetwork
vertextype(::Type{G}) where {G<:NetOrDiNet} = Int

#### GRAPH CONSTRUCTORS
function DiNetwork(n::Integer = 0)
    out_edges = [Vector{Pair{Int,Int}}() for _=1:n]
    in_edges = [Vector{Pair{Int,Int}}() for _=1:n]

    epos = Vector{Pair{Int,Int}}()
    free_indexes = Vector{Int}()
    return DiNetwork(0, 0, out_edges, in_edges, epos, free_indexes, PropertyStore())
end

nv(g::NetOrDiNet) = length(g.out_edges)
ne(g::NetOrDiNet) = g.ne

function add_vertex!(g::DiNetwork)
    push!(g.in_edges, Vector{Pair{Int,Int}}())
    push!(g.out_edges, Vector{Pair{Int,Int}}())
    return nv(g)
end

function add_edge!(g::DiNetwork, u::Integer, v::Integer)
    (u in vertices(g) && v in vertices(g)) || return (false, IndexedEdge(u,v,-1))
    has_edge(g, u, v) && return (false, IndexedEdge(u,v,-1))
    if isempty(g.free_indexes)
        g.edge_index_range += 1
        idx = g.edge_index_range
    else
        idx = pop!(g.free_indexes)
    end
    oes = g.out_edges[u]
    ies = g.in_edges[v]
    push!(oes, Pair(v, idx))
    push!(ies, Pair(u, idx))
    g.ne += 1

    length(g.epos) < idx && resize!(g.epos, idx)
    g.epos[idx] = Pair(length(oes), length(ies))

    return (true, IndexedEdge(u,v,idx))
end

rem_edge!(g::NetOrDiNet, s::Integer, t::Integer) = rem_edge!(g, edge(g, s, t))

function rem_edge!(g::DiNetwork, e::IndexedEdge)
    s = e.src
    t = e.dst
    idx = e.idx
    idx <= 0 && return false
    oes = g.out_edges[s]
    ies = g.in_edges[t]

    idx > length(g.epos) && return false
    length(oes) == 0 && return false
    p1 = g.epos[idx].first
    p1 < 0 && return false
    back = last(oes)
    p1 = g.epos[idx].first
    p2 = g.epos[back.second].second
    g.epos[back.second] = Pair(p1, p2)
    oes[p1] = back
    pop!(oes)

    back = last(ies)
    p1 = g.epos[back.second].first
    p2 = g.epos[idx].second
    g.epos[back.second] = Pair(p1, p2)
    ies[p2] = back
    pop!(ies)

    g.epos[idx] = Pair(-1,-1)

    g.ne -= 1
    push!(g.free_indexes, idx)
    return true
end

# TODO can be improved (see graph/digraph)
function edge(g::NetOrDiNet, i::Integer, j::Integer)
    (i > nv(g) || j > nv(g)) && return IndexedEdge(i, j, -1)
    oes = g.out_edges[i]
    pos = findfirst(e->e.first==j, oes)
    if pos !== nothing
        return IndexedEdge(i, j, oes[pos].second)
    else
        return IndexedEdge(i, j, -1)
    end
end

function out_edges(g::NetOrDiNet, i::Integer)
    oes = g.out_edges[i]
    return (IndexedEdge(i, j, idx) for (j, idx) in oes)
end

function out_neighbors(g::NetOrDiNet, i::Integer)
    oes = g.out_edges[i]
    return (j for (j, idx) in oes)
end

function in_edges(g::DiNetwork, i::Integer)
    ies = g.in_edges[i]
    return (IndexedEdge(j, i, idx) for (j, idx) in ies)
end

function in_neighbors(g::DiNetwork, i::Integer)
    ies = g.in_edges[i]
    return (j for (j, idx) in ies)
end

pop_vertex!(g::Network) = (clean_vertex!(g, nv(g)); pop!(g.out_edges); nv(g)+1)
pop_vertex!(g::DiNetwork) = (clean_vertex!(g, nv(g)); pop!(g.out_edges);
                          pop!(g.in_edges); nv(g)+1)

function reverse!(g::DiNetwork)
    g.out_edges, g.in_edges = g.in_edges, g.out_edges
    for i=1:length(g.epos)
        g.epos[i] = reverse(g.epos[i])
    end
    return g
end

## GRAPH

function Network(n::Integer = 0)
    out_edges = [Vector{Pair{Int,Int}}() for _=1:n]
    epos = Vector{Pair{Int,Int}}()
    free_indexes = Vector{Int}()
    return Network(0, 0, out_edges, epos, free_indexes, PropertyStore())
end

function add_vertex!(g::Network)
    push!(g.out_edges, Vector{Pair{Int,Int}}())
    return nv(g)
end

function add_edge!(g::Network, u::Integer, v::Integer)
    u, v = u <= v ? (u, v) : (v, u)
    (u in vertices(g) && v in vertices(g)) || return (false, IndexedEdge(u,v,-1))
    has_edge(g, u, v) && return (false, IndexedEdge(u,v,-1)) # could be removed for multigraphs

    if isempty(g.free_indexes)
        g.edge_index_range += 1
        idx = g.edge_index_range
    else
        idx = pop!(g.free_indexes)
    end
    oes = g.out_edges[u]
    ies = g.out_edges[v]
    push!(oes, Pair(v, idx))
    if u != v
        push!(ies, Pair(u, idx))
    end
    g.ne += 1

    length(g.epos) < idx && resize!(g.epos, idx)
    g.epos[idx] = Pair(length(oes), length(ies))

    return (true, IndexedEdge(u,v,idx))
end

function rem_edge!(g::Network, e::IndexedEdge)
    s = e.src
    t = e.dst
    if s > t
        s,t = t,s
    end
    idx = e.idx
    idx <= 0 && return false
    oes = g.out_edges[s]
    ies = g.out_edges[t]
    idx > length(g.epos) && return false
    length(oes) == 0 && return false
    p1 = g.epos[idx].first
    p1 < 0 && return false

    back = last(oes)
    if back.first > s
        p2 = g.epos[back.second].second
        g.epos[back.second] = Pair(p1 , p2)
    elseif back.first == s #fix self-edges
        g.epos[back.second] = Pair(p1, p1)
    else
        p2 = g.epos[back.second].first
        g.epos[back.second] = Pair(p2 , p1)
    end
    oes[p1] = back
    pop!(oes)

    if s != t
        back = last(ies)
        p1 = g.epos[idx].second
        if back.first > t
            p2 = g.epos[back.second].second
            g.epos[back.second] = Pair(p1 , p2)
        elseif back.first == t #fix self-edges
            g.epos[back.second] = Pair(p1 , p1)
        else
            p2 = g.epos[back.second].first
            g.epos[back.second] = Pair(p2 , p1)
        end
        ies[p1] = back
        pop!(ies)
    end

    g.epos[idx] = Pair(-1,-1)

    g.ne -= 1
    push!(g.free_indexes, idx)
    return true
end

function in_edges(g::Network, i::Integer)
    ies = g.out_edges[i]
    return (IndexedEdge(j, i, idx) for (j, idx) in ies)
end

function swap_vertices!(g::Network, u::Integer, v::Integer)
    if u != v
        #TODO copying to avoid problems with self edges
        # maybe can copy only one of the two
        neigu = deepcopy(g.out_edges[u])
        neigv = deepcopy(g.out_edges[v])

        for (k,p) in enumerate(neigu)
            j, idx = p
            kj = j <= u ? g.epos[idx].first : g.epos[idx].second
            g.out_edges[j][kj] = Pair(v, idx)
            g.epos[idx] = j <= v ? Pair(kj, k) : Pair(k, kj)
        end

        for (k,p) in enumerate(neigv)
            j, idx = p
            kj = j <= v ? g.epos[idx].first : g.epos[idx].second
            g.out_edges[j][kj] = Pair(u, idx)
            g.epos[idx] = j <= u ? Pair(kj, k) : Pair(k, kj)
        end

        g.out_edges[u], g.out_edges[v] = g.out_edges[v], g.out_edges[u]

        swap_vertices!(g.props, u, v)
    end
end

function swap_vertices!(g::DiNetwork, u::Integer, v::Integer)
    if u != v
        #TODO copying to avoid problems with self edges
        # maybe can copy only one of the two
        neigu = deepcopy(g.out_edges[u])
        neigv = deepcopy(g.out_edges[v])
        neiguin = deepcopy(g.in_edges[u])
        neigvin = deepcopy(g.in_edges[v])

        for (k,p) in enumerate(neigu)
            j, idx = p
            kj = g.epos[idx].second
            g.in_edges[j][kj] = Pair(v, idx)
        end

        for (k,p) in enumerate(neigv)
            j, idx = p
            kj = g.epos[idx].second
            g.in_edges[j][kj] = Pair(u, idx)
        end

        for (k,p) in enumerate(neiguin)
            j, idx = p
            kj = g.epos[idx].first
            g.out_edges[j][kj] = Pair(v, idx)
        end

        for (k,p) in enumerate(neigvin)
            j, idx = p
            kj = g.epos[idx].first
            g.out_edges[j][kj] = Pair(u, idx)
        end

        g.out_edges[u], g.out_edges[v] = g.out_edges[v], g.out_edges[u]
        g.in_edges[u], g.in_edges[v] = g.in_edges[v], g.in_edges[u]

        swap_vertices!(g.props, u, v)
    end
end

# function test_consistency(g::Network)
#     for i=1:nv(g)
#         for (k, p) in  enumerate(g.out_edges[i])
#             j = p.first
#             id = p.second
#             if i < j
#                 @assert g.epos[id].first == k "$id $i $j $(g.epos[id]) $(g.out_edges[i])"
#             else
#                 @assert g.epos[id].second == k
#             end
#             @assert findfirst(e->e.first==i, g.out_edges[j]) > 0
#         end
#     end
# end
#
# function test_consistency(g::DiNetwork)
#     for i=1:nv(g)
#         for (k, p) in  enumerate(g.out_edges[i])
#             j = p.first
#             id = p.second
#             @assert g.epos[id].first == k "$id $i $j $(g.epos[id]) $(g.out_edges[i])"
#             posin = findfirst(e->e.first==i, g.in_edges[j])
#             @assert posin > 0
#             @assert g.in_edges[j][posin].second == id
#         end
#     end
# end


# When an object like DimArray or DimStack is constructed,
# `format` is called on its dimensions check that the axes match,
# and fill in any `Auto-` fields.

"""
    format(dims, x) => Tuple{Vararg{<:Dimension,N}}

Format the passed-in dimension(s) `dims` to match the object `x`.

This means converting indexes of `Tuple` to `LinRange`, and running
`format`. Errors are also thrown if dims don't match the array dims or size.

If a [`LookupArray`](@ref) hasn't been specified, a lookup is chosen
based on the type and element type of the index.
"""
format(dims, A::AbstractArray) = format((dims,), A)
function format(dims::NamedTuple, A::AbstractArray)
    dims = map(keys(dims), values(dims)) do k, v
        rebuild(key2dim(k), v)
    end
    return format(dims, axes(A))
end
format(dims::Tuple{Vararg{<:Any,N}}, A::AbstractArray{<:Any,N}) where N =
    format(dims, axes(A))
@noinline format(dims::Tuple{Vararg{<:Any,M}}, A::AbstractArray{<:Any,N}) where {N,M} =
    throw(DimensionMismatch("Array A has $N axes, while the number of dims is $M: $(map(basetypeof, dims))"))
format(dims::Tuple, axes::Tuple) = map(_format, dims, axes)

_format(dimname::Symbol, axis::AbstractRange) = Dim{dimname}(NoLookup(axes(axis, 1)))
_format(::Type{D}, axis::AbstractRange) where D<:Dimension = D(NoLookup(axes(axis, 1)))
_format(dim::Dimension{Colon}, axis::AbstractRange) = rebuild(dim, NoLookup(axes(axis, 1)))
function _format(dim::Dimension, axis::AbstractRange)
    newlookup = format(val(dim), basetypeof(dim), axes(axis, 1))
    checkaxis(newlookup, axis)
    return rebuild(dim, newlookup)
end

format(val::AbstractArray, D::Type, axis::AbstractRange) = format(AutoLookup(), D, val, axis)
format(m::LookupArray, D::Type, axis::AbstractRange) = format(m, D, parent(m), axis)

# Format LookupArrays
# No more identification required for NoLookup
format(m::NoLookup, D::Type, index, axis::AbstractRange) = m
format(m::NoLookup, D::Type, index::AutoIndex, axis::AbstractRange) = NoLookup(axis)
# # AutoLookup
function format(m::AutoLookup, D::Type, index::AbstractArray{T}, axis::AbstractRange) where T
    # A mixed type index is Categorical
    m = if isconcretetype(T)
        Sampled(; order=order(m), span=span(m), sampling=sampling(m), metadata=metadata(m))
    else
        o = order(m) isa AutoOrder ? Unordered() : order(m)
        Categorical(; order=o, metadata=metadata(m))
    end
    format(m, D, index, axis)
end
function format(m::AutoLookup, D::Type, index::AbstractArray{<:CategoricalEltypes}, axis::AbstractRange)
    o = _format(order(m), D, index)
    return Categorical(index; order=o, metadata=metadata(m))
end
function format(m::Categorical, D::Type, index, axis::AbstractRange)
    i = _format(index, axis)
    o = _format(order(m), D, index)
    return rebuild(m; data=i, order=o)
end
# # Sampled
function format(m::AbstractSampled, D::Type, index, axis::AbstractRange)
    i = _format(index, axis)
    o = _format(order(m), D, index)
    sp = _format(span(m), D, index)
    sa = _format(sampling(m), sp, D, index)
    x = rebuild(m; data=i, order=o, span=sp, sampling=sa)
    return x
end
# # Transformed
format(m::Transformed, D::Type, index::AutoIndex, axis::AbstractRange) = rebuild(m; data=axis)
format(m::Transformed, D::Type, index, axis::AbstractRange) = m

# Index
_format(index::AbstractArray, axis::AbstractRange) = index
_format(index::AutoLookup, axis::AbstractRange) = axis
# Order
_format(order::Order, D::Type, index) = order
_format(order::AutoOrder, D::Type, index) = LookupArrays.orderof(index)
# Span
_format(span::AutoSpan, D::Type, index::Union{AbstractArray,Val}) =
    _format(Irregular(), D, index)
_format(span::AutoSpan, D::Type, index::AbstractRange) = Regular(step(index))
_format(span::Regular{AutoStep}, D::Type, index::Union{AbstractArray,Val}) = _arraynosteperror()
_format(span::Regular{AutoStep}, D::Type, index::AbstractRange) = Regular(step(index))
_format(span::Regular, D::Type, index::Union{AbstractArray,Val}) = span
function _format(span::Regular, D::Type, index::AbstractRange)
    step(span) isa Number && !(step(span) ≈ step(index)) && _steperror(index, span)
    return span
end
_format(span::Irregular{AutoBounds}, D, index) = Irregular(nothing, nothing)
_format(span::Irregular{<:Tuple}, D, index) = span
_format(span::Explicit, D, index) = span
# Sampling
_format(sampling::AutoSampling, span::Span, D::Type, index) = Points()
_format(sampling::AutoSampling, span::Explicit, D::Type, index) =
    Intervals(_format(locus(sampling), D, index))
_format(sampling::Points, span::Span, D::Type, index) = sampling
_format(sampling::Points, span::Explicit, D::Type, index) = _explicitpoints_error()
_format(sampling::Intervals, span::Span, D::Type, index) =
    rebuild(sampling, _format(locus(sampling), D, index))
# Locus
_format(locus::AutoLocus, D::Type, index) = Center()
# Time dimensions need to default to the Start() locus, as that is
# nearly always the _format and Center intervals are difficult to
# calculate with DateTime step values.
_format(locus::AutoLocus, D::Type{<:TimeDim}, index) = Start()
_format(locus::Locus, D::Type, index) = locus

_order(index) = first(index) <= last(index) ? ForwardOrdered() : ReverseOrdered()

@noinline _explicitpoints_error() =
    throw(ArgumentError("Cannot use Explicit span with Points sampling"))
@noinline _steperror(index, span) =
    throw(ArgumentError("lookup step $(step(span)) does not match index step $(step(index))"))
@noinline _arraynosteperror() =
    throw(ArgumentError("`Regular` must specify `step` size with an index other than `AbstractRange`"))

checkaxis(lookup::Transformed, axis) = nothing
function checkaxis(lookup, axis)
    if !(first(axes(lookup)) == axis)
        throw(DimensionMismatch(
            "axes of $(basetypeof(lookup)) of $(first(axes(lookup))) do not match array axis of $axis"
        ))
    end
end


struct TsptwTrajectoryState <: GraphTrajectoryState
    fg::GeometricFlux.FeaturedGraph
    variableIdx::Int
    possibleValuesIdx::AbstractVector{Int}
end

Flux.@functor TsptwTrajectoryState


__precompile__()

module DSPRemezWrap

include("remez_wrap.jl")
export  remez_wrap,
        RemezFilterType,
        filter_type_bandpass,
        filter_type_differentiator,
        filter_type_hilbert

end


# ------------------------------------------------------------------
# Licensed under the MIT License. See LICENSE in the project root.
# ------------------------------------------------------------------

module GeoTables

using Meshes

import Tables
import Meshes

import GADM
import Shapefile as SHP
import ArchGDAL as AG
import GeoInterface as GI

include("conversion.jl")
include("geotable.jl")

"""
    load(fname, layer=0)

Load geospatial table from file `fname` and convert the
`geometry` column to Meshes.jl geometries. Optionally,
specify the layer of geometries to read in the file.

Currently supported file types are:

- `*.shp` via Shapefile.jl
- Other formats via ArchGDAL.jl
"""
function load(fname, layer=0)
  if endswith(fname, ".shp")
    table = SHP.Table(fname)
  else # fallback to GDAL
    file  = AG.read(fname)
    data  = AG.getlayer(file, layer)
    table = Tables.columns(data)
  end
  GeoTable(table)
end

"""
    gadm(country, subregions...; children=true)

(Down)load GADM table using `GADM.get` and convert
the `geometry` column to Meshes.jl geometries.

If `children` is `true`, return the table with the
geometries of the subregions, otherwise return a single
geometry for the final subregion in the specification.
"""
function gadm(country, subregions...; children=false)
  data  = GADM.get(country, subregions...; children=children)
  table = children ? data[2] : data
  GeoTable(table)
end

end


function test15()
  filename = "test15.asc"
  # exectuablepath = null string will not run LTspice.exe.  Test parsing only.
  sim = LTspiceSimulation(filename,executablepath="")
  @test LTspice.does_circuitfilearray_file_match(sim)
  show(IOBuffer(),sim)
  @test measurementnames(sim) == ("σ", "a", "ψπππππ")
  @test parameternames(sim) == ("m", "μ", "MEG", "Ω", "θ", "Δ", "Φ", "ψ")
end
test15()


struct DiffuseLight <: Material
    emit::Texture
end

function scatter(light::DiffuseLight, ray::Ray, hp::HitPoint)::Tuple{Bool, Union{Nothing, AbstractVec}, Union{Nothing, Ray}}
    false, nothing, nothing
end

function emit(light::DiffuseLight, u::AbstractFloat, v::AbstractFloat, point::AbstractVec)::AbstractVec
    value(light.emit, u, v, point)
end


module Models

export Model, critical_point, local_boltzmann_weight, mpo, localham, checklocalham

struct Model{model} end

Model(s::AbstractString) = Model{Symbol(s)}()

# For notation:
# Model"tfim" == Model{:tfim}
# Model"heisenberg" == Model{:heisenberg}
macro Model_str(s)
  return :(Model{$(Expr(:quote, Symbol(s)))})
end

include("ising_classical_2d.jl")
include("hamiltonians.jl")

end


module SimulacoesTemplate

  f(x) = 2*x

  export f

end


"""
    AbstractFitFunction{T, ND, NP}

Abstract type for an `ND`-dimensional fit of eltype `T` with `NP` parameters.
"""
abstract type AbstractFitFunction{T, ND, NP} end

"""
    struct FitFunction{T, ND, NP} <: AbstractFitFunction{T, ND, NP}

T: Precision type

ND: Dimensionality

NP: NP parameters

Fields:
- `model::Function`: Function of the fit model.
- `fitranges::NTuple{N, Vector{T}}`: Range on which the fit is performed.
- `parameter_names::Vector{Symbol}`: Parameters names.
- `fitted_parameters::Vector{T}`: Fitted parameters.
- `initial_parameters::Vector{T}`: Initial parameters.

"""
struct FitFunction{T, ND, NP} <: AbstractFitFunction{T, ND, NP}
    model::Function
    fitranges::NTuple{ND, Vector{T}}
    parameter_names::Vector{Symbol}
    fitted_parameters::Vector{T}
    initial_parameters::Vector{T}

    function FitFunction{T}(model::Function, ndims::Int, nparams::Int) where {T <: AbstractFloat}
        fitranges::NTuple{ndims, Vector{T}} = NTuple{ndims, Vector{T}}( [-Inf, Inf] for idim in 1:ndims)
        parameter_names::Vector{Symbol} = [Symbol("par$(ipar)") for ipar in 1:nparams]
        fitted_parameters::Vector{T} = [ T(NaN) for ipar in 1:nparams]
        initial_parameters::Vector{T} = [ T(NaN) for ipar in 1:nparams]
        return new{T, ndims, nparams}(model, fitranges, parameter_names, fitted_parameters, initial_parameters)
    end
end

get_ndims(ff::FitFunction{T, ND}) where {T <: AbstractFloat, ND} = ND
get_nparams(ff::FitFunction{T, ND, NP}) where {T <: AbstractFloat, ND, NP} = NP

function set_fitranges!(ff::FitFunction{T, N}, fitranges::NTuple{N, NTuple{2, <:Real}})::Nothing where {T <: AbstractFloat, N}
     for idim in 1:N
        ff.fitranges[idim][:] = T.([ fitranges[idim][1], fitranges[idim][2] ])
    end
    nothing
end
function set_fitranges!(ff::FitFunction{T, N}, fitranges::NTuple{N, AbstractVector{<:Real}})::Nothing where {T <: AbstractFloat, N}
    for idim in 1:N
        @assert length(fitranges[idim]) == 2 "All vectors in `fitranges` must have length 2. But in dimension $(idim) it is $(length(fitranges[idim])) ($(fitranges[idim]))."
        ff.fitranges[idim][:] = T.([ first(fitranges[idim]), last(fitranges[idim]) ])
    end
    nothing
end
function set_parameter_names!(ff::FitFunction{T}, parameter_names::Vector{String})::Nothing where {T <: AbstractFloat}
    ff.parameter_names[:] = [Symbol(name) for name in parameter_names]
    nothing
end
function set_initial_parameters!(ff::FitFunction{T}, initial_parameters::NamedTuple)::Nothing where {T <: AbstractFloat}
    ff.parameter_names[:] = collect(keys(initial_parameters))
    ff.initial_parameters[:] = collect(T, initial_parameters)
    nothing
end
function set_initial_parameters!(ff::FitFunction{T}, initial_parameters::AbstractVector{<:Real})::Nothing where {T <: AbstractFloat}
    ff.initial_parameters[:] = collect(T, initial_parameters)
    nothing
end
function _set_fitted_parameters!(ff::FitFunction{T}, fitted_parameters::AbstractVector{<:Real})::Nothing where {T <: AbstractFloat}
    ff.fitted_parameters[:] = T.(fitted_parameters)
    nothing
end
function _set_fitted_parameters!(ff::FitFunction{T}, fitted_parameters::NamedTuple)::Nothing where {T <: AbstractFloat}
    ff.fitted_parameters[:] = collect(T, fitted_parameters)
    nothing
end

function get_fitted_parameters(ff::FitFunction{T, ND, NP}) where {T <: AbstractFloat, ND, NP}
    return NamedTuple{Tuple(ff.parameter_names), NTuple{NP, T}}( ff.fitted_parameters )
end
function get_initial_parameters(ff::FitFunction{T, ND, NP}) where {T <: AbstractFloat, ND, NP}
    return NamedTuple{Tuple(ff.parameter_names), NTuple{NP, T}}( ff.initial_parameters )
end

function println(io::IO, f::FitFunction)
    println(io, "Model: ", f.model)
    println(io, "  fit ranges: ", f.fitranges)
    println(io, "  Parameters: value (initial value)")
    for i in eachindex(f.parameter_names)
        println("    ", f.parameter_names[i], ": ", f.fitted_parameters[i], " (", f.initial_parameters[i],")" )
    end
end
function print(io::IO, f::FitFunction)
    println(io, f)
end
function show(io::IO, f::FitFunction)
    println(io, f)
end
function show(io::IO, ::MIME"text/plain", f::FitFunction)
    show(io, f)
end

@recipe function f(ff::FitFunction; npoints = 501, use_initial_parameters = false)
    x = collect(range(ff.fitranges[1][1], stop=ff.fitranges[1][2], length=npoints))
    par = use_initial_parameters ? ff.initial_parameters : ff.fitted_parameters
    y = ff.model(x, collect(par))
    linecolor --> (use_initial_parameters ? :green : :red)
    label --> (use_initial_parameters ? "Fit model with initial parameters" : "Fit model with fitted parameters")
    x,y
end


using HDF5, DataFrames, CSV, JLD
using StatsBase, Polynomials, CurveFit
using PyPlot

kep_filename = joinpath(Pkg.dir("ExoplanetsSysSim"), "data", "q1_q17_dr25_stellar.csv")
gaia_filename = joinpath(Pkg.dir("ExoplanetsSysSim"), "data", "gaiadr2_keplerdr25_crossref.csv")
mast_filename = joinpath(Pkg.dir("ExoplanetsSysSim"), "data", "KeplerMAST_TargetProperties.csv")
stellar_catalog_file_out = joinpath(Pkg.dir("ExoplanetsSysSim"), "data", "q1q17_dr25_gaia_fgk.jld")

kep_df = CSV.read(kep_filename)
gaia_df = CSV.read(gaia_filename, rows_for_type_detect=30000)
mast_df = CSV.read(mast_filename)
tmass_df = CSV.read(tmass_filename, rows_for_type_detect=30000)

dup_gaiaid = find(nonunique(DataFrame(x = gaia_df[:source_id])))
deleterows!(gaia_df, dup_gaiaid)

println("Total crossref target stars = ", length(gaia_df[:kepid]))

mag_diff = gaia_df[:phot_g_mean_mag].-gaia_df[:kepmag]
quant_arr = quantile(mag_diff, [0.067,0.933])   # 1.5-sigma cut
mag_match = find(x->quant_arr[1]<=x<=quant_arr[2], mag_diff)
gaia_df = gaia_df[mag_match,:]

gaia_col = [:kepid,:source_id,:parallax,:parallax_error,:astrometric_gof_al,:astrometric_excess_noise_sig,:phot_g_mean_mag,:bp_rp,:a_g_val,:priam_flags,:teff_val,:teff_percentile_lower,:teff_percentile_upper,:radius_val,:radius_percentile_lower,:radius_percentile_upper,:lum_val,:lum_percentile_lower,:lum_percentile_upper]
df = join(kep_df, gaia_df[:,gaia_col], on=:kepid)
df = join(df, mast_df, on=:kepid)
kep_df = nothing
gaia_df = nothing

println("Total target stars (KOIs) matching magnitude = ", length(df[:kepid]), " (", sum(df[:nkoi]),")")

df[:teff] = df[:teff_val]
df[:teff_err1] = df[:teff_percentile_upper].-df[:teff_val]
df[:teff_err2] = df[:teff_percentile_lower].-df[:teff_val]
delete!(df, :teff_val)
delete!(df, :teff_percentile_upper)
delete!(df, :teff_percentile_lower)
for x in 1:length(df[:kepid])
    if !isnan(df[x,:radius_val])
        df[x,:radius_err1] = df[x,:radius_percentile_upper]-df[x,:radius_val]
        df[x,:radius_err2] = df[x,:radius_percentile_lower]-df[x,:radius_val]
        df[x,:radius] = df[x,:radius_val]
    end
end
delete!(df, :radius_val)
delete!(df, :radius_percentile_upper)
delete!(df, :radius_percentile_lower)

not_binary = (df[:astrometric_gof_al] .<= 20) .& (df[:astrometric_excess_noise_sig] .<= 5)
astrometry_good = []
for x in 1:length(df[:kepid])
    if !(ismissing(df[x,:priam_flags]))
        pflag = string(df[x,:priam_flags])
         if (pflag[2] == '0') & (pflag[3] == '0') # WARNING: Assumes flag had first '0' removed by crossref script
             push!(astrometry_good, true)
         else
             push!(astrometry_good, false)
         end
     else
         push!(astrometry_good, false)
     end
end
astro_good = astrometry_good .& (df[:parallax_error] .< 0.1*df[:parallax])
planet_search = df[:kepmag] .<= 16.

has_mass = .! (ismissing.(df[:mass]) .| ismissing.(df[:mass_err1]) .| ismissing.(df[:mass_err2]))
has_radius = .! (ismissing.(df[:radius]) .| ismissing.(df[:radius_err1]) .| ismissing.(df[:radius_err2]))# .| isnan.(df[:radius]) .| isnan.(df[:radius_err1]) .| isnan.(df[:radius_err2]))
has_dens = .! (ismissing.(df[:dens]) .| ismissing.(df[:dens_err1]) .| ismissing.(df[:dens_err2]))
has_cdpp = .! (ismissing.(df[:rrmscdpp01p5]) .| ismissing.(df[:rrmscdpp02p0]) .| ismissing.(df[:rrmscdpp02p5]) .| ismissing.(df[:rrmscdpp03p0]) .| ismissing.(df[:rrmscdpp03p5]) .| ismissing.(df[:rrmscdpp04p5]) .| ismissing.(df[:rrmscdpp05p0]) .| ismissing.(df[:rrmscdpp06p0]) .| ismissing.(df[:rrmscdpp07p5]) .| ismissing.(df[:rrmscdpp09p0]) .| ismissing.(df[:rrmscdpp10p5]) .| ismissing.(df[:rrmscdpp12p0]) .| ismissing.(df[:rrmscdpp12p5]) .| ismissing.(df[:rrmscdpp15p0]))
has_rest = .! (ismissing.(df[:dataspan]) .| ismissing.(df[:dutycycle]))
has_limbd = .! (ismissing.(df[:limbdark_coeff1]) .| ismissing.(df[:limbdark_coeff2]) .| ismissing.(df[:limbdark_coeff3]) .| ismissing.(df[:limbdark_coeff4]))

mast_cut =.&(df[:numLCEXqtrs].>0,df[:numLCqtrs].>4)

is_usable = .&(has_radius, has_mass, has_rest, has_cdpp, has_dens, astro_good, not_binary, planet_search, mast_cut)

df = df[find(is_usable),:]
println("Total stars (KOIs) with valid parameters = ", length(df[:kepid]), " (", sum(df[:nkoi]),")")

fgk_color = (0.5 .<= df[:bp_rp] .<= 1.7)
df = df[find(fgk_color),:]
println("Total FGK colored stars (KOIs) with valid parameters = ", length(find(fgk_color)), " (", sum(df[:, :nkoi]),")")

near_FGK_MS = []
coeff = [2.5,-3.6,0.9]
for i in 1:6
    near_FGK_MS = (log10.(df[:lum_val]).< map(x->polyval(Poly(coeff),x),df[:bp_rp]) + log10(1.75))
    coeff = poly_fit(df[near_FGK_MS,:bp_rp],log10.(df[near_FGK_MS,:lum_val]),2)
end
FGK = find(near_FGK_MS)

println("Total FGK stars (KOIs) with valid parameters = ", length(FGK), " (", sum(df[FGK, :nkoi]),")")

plot_samp = rand(1:length(df[:kepid]), 5000)
plt[:scatter](df[FGK, :bp_rp], df[FGK, :lum_val], s=3, label = "FGK", color="red")
plt[:scatter](df[plot_samp, :bp_rp], df[plot_samp, :lum_val], s=3, label = "All", color="black")
plt[:ylabel](L"$L (L_\odot)$")
plt[:xlabel](L"$B_p - R_p$")
plt[:yscale]("log")
plt[:ylim](reverse(plt[:ylim]()))
plt[:legend]()
plt[:savefig]("fgk-dwarf_samp.png")

# See options at: http://exoplanetarchive.ipac.caltech.edu/docs/API_keplerstellar_columns.html
# TODO SCI DETAIL or IMPORTANT?: Read in all CDPP's, so can interpolate?
symbols_to_keep = [ :kepid, :source_id, :mass, :mass_err1, :mass_err2, :radius, :radius_err1, :radius_err2, :dens, :dens_err1, :dens_err2, :teff, :phot_g_mean_mag, :bp_rp, :lum_val, :rrmscdpp01p5, :rrmscdpp02p0, :rrmscdpp02p5, :rrmscdpp03p0, :rrmscdpp03p5, :rrmscdpp04p5, :rrmscdpp05p0, :rrmscdpp06p0, :rrmscdpp07p5, :rrmscdpp09p0, :rrmscdpp10p5, :rrmscdpp12p0, :rrmscdpp12p5, :rrmscdpp15p0, :dataspan, :dutycycle, :limbdark_coeff1, :limbdark_coeff2, :limbdark_coeff3, :limbdark_coeff4, :contam]
# delete!(df, [~(x in symbols_to_keep) for x in names(df)])    # delete columns that we won't be using anyway
df = df[FGK, symbols_to_keep]
tmp_df = DataFrame()
for col in names(df)
    tmp_df[col] = collect(skipmissing(df[col]))
end
df = tmp_df

save(stellar_catalog_file_out,"stellar_catalog", df)


# This file is a part of ValueShapes.jl, licensed under the MIT License (MIT).

using ValueShapes
using Test

@testset "const_value_shape" begin
    @inferred(size(ConstValueShape(42))) == ()
    @inferred(eltype(ConstValueShape(42))) == Int
    @inferred(totalndof(ConstValueShape(42))) == 0

    @inferred(size(ConstValueShape(rand(2,3)))) == (2,3)
    @inferred(ValueShapes.default_unshaped_eltype(ConstValueShape(rand(Float32,2,3)))) == Float32
    @inferred(totalndof(ConstValueShape(rand(2,3)))) == 0

    @test @inferred(ConstValueShape([1 4; 3 2])(undef)) == [1 4; 3 2]
    @test @inferred(ConstValueShape([1 4; 3 2])(Int[])) == [1 4; 3 2]

    shape = ConstValueShape([1 4; 3 2])

    @test typeof(@inferred(Vector{Int32}(undef, shape))) == Vector{Int32}
    @test size(@inferred(Vector{Int32}(undef, shape))) == (0,)
end


@testset "history.jl" begin

        @testset ":tally" begin
            global tallied = Variable{Int64}(data=:tally, history=true)

            @process test_process(value_durations) begin
                for (value, duration) in value_durations
                    tallied.value = value
                    work(duration)
                end
            end

            @simulation begin
                value_durations = [(1, 2.0), (2, 1.0), (3, 2.0), (4, 3.0)]

                @schedule now test_process(value_durations)
                @schedule at 1.0 test_process(value_durations)
                @schedule in 3.0 test_process(value_durations)

                start_simulation()
            end

            want = [1, 1, 2, 1, 2, 3, 3, 2, 4, 4, 3, 4]
            @test tallied.history.data == want
        end

        @testset ":accumulate" begin
            global accumulated = Variable{Int64}(data=:accumulate, history=true)

            @process test_process(value_durations) begin
                for (value, duration) in value_durations
                    accumulated.value = value
                    work(duration)
                end
            end

            @simulation begin
                value_durations = [(1, 2.0), (2, 1.0), (3, 2.0), (4, 3.0)]

                @schedule now test_process(value_durations)
                @schedule at 1.0 test_process(value_durations)
                @schedule in 3.0 test_process(value_durations)

                start_simulation()
            end

            want = [1, 2, 1, 2, 3, 2, 4, 3, 4]
            @test accumulated.history.data == want

            want = [2.0, 1.0, 0.0, 0.0, 2.0, 0.0, 1.0, 2.0, 3.0]
            @test accumulated.history.durations == want
        end
end


import MCMCChains: Chains

function create_a3d(noofsamples, noofvariables, noofchains)
   a3d = fill(0.0, noofsamples, noofvariables, noofchains)
   a3d
 end

 function insert_chain!(a3d, chain, posterior, trans)
   for i in 1:size(a3d, 1)
     a3d[i,:,chain] = inverse(trans, posterior[i])
   end
 end

 function insert_chain!(a3d, chain, posterior)
   for i in 1:size(a3d, 1)
     a3d[i,:,chain] = posterior[i, :]
   end
 end

 function create_mcmcchains(a3d, cnames;start=1)
   Chains(a3d, cnames; start=start)
 end

 function create_mcmcchains(a3d, cnames, sections::Dict{Symbol, Vector{String}};
   start=1)
   Chains(a3d, cnames, sections; start=start)
 end


mutable struct SeqRing{T <: FieldElem} <: Ring
    base_ring::PolyRing{T}

    function SeqRing{T}(R::PolyRing, cached::Bool = true) where T <: FieldElem
       if cached && haskey(SequenceID, R)
          return SequenceID[R]::SeqRing{T}
       else
          z = new{T}(R)
          if cached
            SequenceID[R] = z
          end
          return z
       end
    end
end

const SequenceID = Dict{Ring, Ring}()

function SequenceRing(R::Ring, s::AbstractString; cached::Bool = true)
    S, v = PolynomialRing(R, s)
    parent_obj = SeqRing{elem_type(base_ring(S))}(S, cached)
    return parent_obj, v
end

SequenceRing(R::PolyRing; cached::Bool = true) = SeqRing{elem_type(base_ring(R))}(R, cached)

function change_coeff_field(F::Field, a::SeqRing{T}) where T <: FieldElem
    R = parent(change_base_ring(F, zero(base_ring(a))))
    SeqRing{elem_type(F)}(R)
end

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

 mutable struct Seq{T <: FieldElem} <: RingElem
    terms::Vector{HyperTerm{T}}
    parent::SeqRing{T}

    Seq{T}() where T <: FieldElem = new{T}(Array{HyperTerm{T}}(undef, 0))

    function Seq{T}(b::Vector{HyperTerm{T}}) where T <: FieldElem
       z = new{T}(b)
       return z
    end
 end

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

parent_type(::Type{Seq{T}}) where T <: FieldElem = SeqRing{T}

elem_type(::Type{SeqRing{T}}) where T <: FieldElem = Seq{T}

base_ring(R::SeqRing{T}) where T <: FieldElem = R.base_ring
base_ring(a::Seq) = base_ring(parent(a))

parent(a::Seq) = a.parent

isdomain_type(::Type{Seq}) = false
isexact_type(::Type{Seq}) = true

Base.iszero(a::Seq) = iszero(length(a.terms))

Base.one(R::SeqRing) = R(1)
Base.zero(R::SeqRing) = R()

Base.one(s::Seq) = one(parent(s))
Base.zero(s::Seq) = zero(parent(s))

function Base.hash(a::Seq, h::UInt)
    b = 0x53dd43cd511044d1%UInt
    for t in a.terms
        b = xor(b, xor(hash(t, h), h))
        b = (b << 1) | (b >> (sizeof(Int)*8 - 1))
    end
    return b
end

function Base.isequal(x::Seq{T}, y::Seq{T}) where {T <: FieldElem}
    parent(x) != parent(y) && return false
    length(x.terms) != length(y.terms) && return false
    for i in 1:length(x.terms)
       x.terms[i] != y.terms[i] && return false
    end
    return true
end

Base.:(==)(x::Seq{T}, y::Seq{T}) where {T <: FieldElem} = isequal(x, y)

canonical_unit(x::Seq) = x

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

function (a::SeqRing{T})() where {T <: FieldElem}
    S = elem_type(base_ring(base_ring(a)))
    z = Seq{S}()
    z.parent = a
    return z
end

function (a::SeqRing{T})(b::Seq{T}) where {T <: FieldElem}
    z = Seq{T}(deepcopy(b.terms))
    z.parent = a
    return z
end

function (a::SeqRing{T})(b::Union{Integer, Rational}) where {T <: FieldElem}
    F = FractionField(base_ring(a))
    return a(F(b))
end

function (a::SeqRing{T})(b::T) where {T <: FieldElem}
    parent(b) != base_ring(base_ring(a)) && error("Unable to coerce to sequence")
    F = FractionField(base_ring(a))
    return a(F(b))
end

function (a::SeqRing{T})(b::PolyElem{T}) where {T <: FieldElem}
    parent(b) != base_ring(a) && error("Unable to coerce to sequence")
    F = FractionField(parent(b))
    return a(F(b))
end

function (a::SeqRing{T})(b::FracElem{<:PolyElem{T}}) where {T <: FieldElem}
    base_ring(b) != base_ring(a) && error("Unable to coerce to sequence")
    # S = elem_type(base_ring(base_ring(a)))
    t = HyperTerm{T}(b)
    z = Seq{T}([t])
    z.parent = a
    return z
end

function (a::SeqRing{T})(b::Union{Integer, Rational}, c::PolyElem{T}) where {T <: FieldElem}
    F = FractionField(base_ring(a))
    return a(F(b), c)
end

function (a::SeqRing{T})(b::T, c::PolyElem{T}) where {T <: FieldElem}
    parent(b) != base_ring(base_ring(a)) && error("Unable to coerce to sequence")
    F = FractionField(base_ring(a))
    return a(F(b), c)
end

function (a::SeqRing{T})(b::PolyElem{T}, c::PolyElem{T}) where {T <: FieldElem}
    parent(b) != base_ring(a) && error("Unable to coerce to sequence")
    F = FractionField(parent(b))
    return a(F(b), c)
end

function (a::SeqRing{T})(b::FracElem{<:PolyElem{T}}, c::PolyElem{T}) where {T <: FieldElem}
    base_ring(b) != base_ring(a) && error("Unable to coerce to sequence")
    S = elem_type(base_ring(base_ring(a)))
    t = HyperTerm{S}(b, c)
    z = Seq{S}([t])
    z.parent = a
    return z
end

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

terms(a::Seq{T}) where {T <: FieldElem} = a.terms
nterms(a::Seq{T}) where {T <: FieldElem} = length(a.terms)

coeffs(a::Seq{T}) where {T <: FieldElem} = iszero(a) ? [FractionField(base_ring(a))(0)] : map(coeff, terms(a))

geometric_terms(a::Seq{T}) where {T <: FieldElem} = map(geometric_term, terms(a))
factorial_terms(a::Seq{T}) where {T <: FieldElem} = map(factorial_term, terms(a))

ishypergeometric(a::Seq{T}) where {T <: FieldElem} = nterms(a) == 1
isrational(a::Seq{T}) where {T <: FieldElem} = iszero(a) || (ishypergeometric(a) && isrational(first(terms(a))))

function Base.gcd(a::Seq{T}, b::Seq{T}) where {T <: FieldElem}
    check_parent(a, b)
    iszero(a) && iszero(b) && return one(a)
    r = reduce(gcd, [a.terms; b.terms])
    z = Seq{T}([r])
    z.parent = a.parent
    return z
end

function Base.div(a::Seq{}, b::Seq{T}) where {T <: FieldElem}
    check_parent(a, b)
    iszero(b) && error("Division by zero")
    !ishypergeometric(b) && error("Division only for hypergeometric sequences supported")
    z = Seq{T}([div(t, b.terms[1]) for t in a.terms])
    z.parent = a.parent
    return z
end

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

needs_parentheses(::Seq) = true
displayed_with_minus_in_front(::Seq) = false

function Base.show(io::IO, a::Seq)
    if iszero(a)
        show(io, 0)
    else
        show(io, a.terms[1])
        for t in a.terms[2:end]
            print(io, " + ")
            show(io, t)
        end
    end
end

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

function check_parent(a::Seq, b::Seq, throw::Bool = true)
    c = parent(a) != parent(b)
    c && throw && error("Incompatible polynomial rings in sequence operation")
    return !c
 end

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

function Base.:-(f::Seq{T}) where {T <: FieldElem}
    z = Seq{T}([-t for t in f.terms])
    z.parent = f.parent
    return z
end

function Base.:+(f::Seq{T}, g::Seq{T}) where T <: FieldElem
    check_parent(f, g)
    terms = deepcopy(f.terms)
    for t in g.terms
        idx = findfirst(x->(issummable(x, t)), terms)
        if idx === nothing
            push!(terms, t)
        else
            terms[idx] += t
        end

    end
    filter!(!iszero, terms)
    isempty(terms) && return zero(f)
    z = Seq{T}(terms)
    z.parent = f.parent
    return z
end

function Base.:-(f::Seq{T}, g::Seq{T}) where T <: FieldElem
    check_parent(f, g)
    terms = deepcopy(f.terms)
    for t in g.terms
        idx = findfirst(x->(issummable(x, t)), terms)
        if idx === nothing
            push!(terms, -t)
        else
            terms[idx] -= t
        end
    end
    filter!(!iszero, terms)
    isempty(terms) && return zero(f)
    z = Seq{T}(terms)
    z.parent = f.parent
    return z
end

function Base.:*(f::Seq{T}, g::Seq{T}) where T <: FieldElem
    check_parent(f, g)
    seqs = Seq{T}[]
    for a in f.terms
        for b in g.terms
            z = Seq{T}([a*b])
            z.parent = f.parent
            push!(seqs, z)
        end
    end
    reduce(+, seqs, init=zero(f))
end

function Base.:^(f::Seq{T}, i::Integer) where T <: FieldElem
    isnegative(i) && error("Only nonnegative powers allowed")
    iszero(i) && return one(f)
    prod(f for _ in 1:i)
end

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

function zero!(f::Seq{T}) where T <: FieldElem
    check_parent(f, g)
    f.terms = Array{HyperTerm{T}}(undef, 0)
    f
end

function add!(f::Seq{T}, g::Seq{T}, h::Seq{T}) where T <: FieldElem
    check_parent(f, g)
    f.terms = (g+h).terms
    f
end

function mul!(f::Seq{T}, g::Seq{T}, h::Seq{T}) where T <: FieldElem
    check_parent(f, g)
    f.terms = (g*h).terms
    f
end

function addeq!(f::Seq{T}, g::Seq{T}) where T <: FieldElem
    check_parent(f, g)
    f.terms = (f+g).terms
    f
end

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

function (a::Seq{T})(b::Integer) where T <: FieldElem
    sum(t(b) for t in a.terms)
end

function (a::Seq{T})(b::Nemo.fmpq_poly) where T <: FieldElem
    # base_ring(a) != parent(b) && error("Polynomial rings do not match")
    (Nemo.degree(b) != 1 || lead(b) != 1 || denominator(Nemo.coeff(b, 0)) != 1) && error("Argument must be of form n+c where c is an integer")
    z = Seq{T}([t(b) for t in a.terms])
    z.parent = a.parent
    return z
end

function change_coeff_field(F::Field, a::Seq{T}) where T <: FieldElem
    ts = [change_coeff_field(F, t) for t in terms(a)]
    z = Seq{elem_type(F)}(ts)
    z.parent = change_coeff_field(F, a.parent)
    return z
end


push!(LOAD_PATH, joinpath(@__DIR__, ".."))

using PkgBenchmark
using Oceananigans
using Benchmarks

baseline = BenchmarkConfig(id="main")
script = joinpath(@__DIR__, "benchmarkable_nonhydrostatic_model.jl")
resultfile = joinpath(@__DIR__, "regression_benchmarks.json")

print_system_info()

judgement = judge(Oceananigans, baseline, script=script, resultfile=resultfile, verbose=true)
results = PkgBenchmark.benchmarkgroup(judgement)

for (case, trial) in results
    println("Results for $case")
    display(trial)
end


# Here we override broadcasting for banded matrices.
# The design is to to exploit the broadcast machinery so that
# banded matrices that conform to the banded matrix interface but are not
# <: AbstractBandedMatrix can get access to fast copyto!, lmul!, rmul!, axpy!, etc.
# using broadcast variants (B .= A, B .= 2.0 .* A, etc.)

abstract type AbstractBlockStyle{N} <: AbstractArrayStyle{N} end
struct BlockStyle{N} <: AbstractBlockStyle{N} end
struct PseudoBlockStyle{N} <: AbstractBlockStyle{N} end

BlockStyle(::Val{N}) where {N} = BlockStyle{N}()
PseudoBlockStyle(::Val{N}) where {N} = PseudoBlockStyle{N}()
BlockStyle{M}(::Val{N}) where {N,M} = BlockStyle{N}()
PseudoBlockStyle{M}(::Val{N}) where {N,M} = PseudoBlockStyle{N}()
BroadcastStyle(::Type{<:BlockArray{<:Any,N}}) where {N} = BlockStyle{N}()
BroadcastStyle(::Type{<:PseudoBlockArray{<:Any,N}}) where {N} = PseudoBlockStyle{N}()
BroadcastStyle(::Type{<:AdjOrTrans{<:Any,<:BlockArray{<:Any,N}}}) where {N} = BlockStyle{2}()
BroadcastStyle(::Type{<:AdjOrTrans{<:Any,<:PseudoBlockArray{<:Any,N}}}) where {N} = PseudoBlockStyle{2}()

BroadcastStyle(::DefaultArrayStyle{N}, b::AbstractBlockStyle{M}) where {M,N} = typeof(b)(Val(max(M,N)))
BroadcastStyle(a::AbstractBlockStyle{N}, ::DefaultArrayStyle{M}) where {M,N} = typeof(a)(Val(max(M,N)))
BroadcastStyle(::StructuredMatrixStyle, b::AbstractBlockStyle{M}) where {M} = typeof(b)(Val(max(M,2)))
BroadcastStyle(a::AbstractBlockStyle{M}, ::StructuredMatrixStyle) where {M} = typeof(a)(Val(max(M,2)))
BroadcastStyle(::BlockStyle{M}, ::PseudoBlockStyle{N}) where {M,N} = BlockStyle(Val(max(M,N)))
BroadcastStyle(::PseudoBlockStyle{M}, ::BlockStyle{N}) where {M,N} = BlockStyle(Val(max(M,N)))

# sortedunion can assume inputs are already sorted so this could be improved
sortedunion(a,b) = sort!(union(a,b))
sortedunion(a::Base.OneTo, b::Base.OneTo) = Base.OneTo(max(last(a),last(b)))
sortedunion(a::AbstractUnitRange, b::AbstractUnitRange) = min(first(a),first(b)):max(last(a),last(b))
combine_blockaxes(a, b) = _BlockedUnitRange(sortedunion(blocklasts(a), blocklasts(b)))

Base.Broadcast.axistype(a::T, b::T) where T<:BlockedUnitRange = length(b) == 1 ? a : combine_blockaxes(a, b)
Base.Broadcast.axistype(a::BlockedUnitRange, b::BlockedUnitRange) = length(b) == 1 ? a : combine_blockaxes(a, b)
Base.Broadcast.axistype(a::BlockedUnitRange, b) = length(b) == 1 ? a : combine_blockaxes(a, b)
Base.Broadcast.axistype(a, b::BlockedUnitRange) = length(b) == 1 ? a : combine_blockaxes(a, b)

similar(bc::Broadcasted{<:AbstractBlockStyle{N}}, ::Type{T}) where {T,N} =
    BlockArray{T,N}(undef, axes(bc))

similar(bc::Broadcasted{PseudoBlockStyle{N}}, ::Type{T}) where {T,N} =
    PseudoBlockArray{T,N}(undef, axes(bc))

"""
    SubBlockIterator(subblock_lasts::Vector{Int}, block_lasts::Vector{Int})
    SubBlockIterator(A::AbstractArray, bs::NTuple{N,AbstractUnitRange{Int}} where N, dim::Integer)

An iterator for iterating `BlockIndexRange` of the blocks specified by
`subblock_lasts`.  The `Block` index part of `BlockIndexRange` is
determined by `subblock_lasts`.  That is to say, the `Block` index first
specifies one of the block represented by `subblock_lasts` and then the
inner-block index range specifies the region within the block.  Each
such block corresponds to a block specified by `blocklasts`.

Note that the invariance `subblock_lasts ⊂ block_lasts` must hold and must
be ensured by the caller.

# Examples
```jldoctest
julia> using BlockArrays

julia> import BlockArrays: SubBlockIterator, BlockIndexRange

julia> A = BlockArray(1:6, 1:3);

julia> subblock_lasts = axes(A, 1).lasts;

julia> @assert subblock_lasts == [1, 3, 6];

julia> block_lasts = [1, 3, 4, 6];

julia> for idx in SubBlockIterator(subblock_lasts, block_lasts)
           B = @show view(A, idx)
           @assert !(parent(B) isa BlockArray)
           idx :: BlockIndexRange
           idx.block :: Block{1}
           idx.indices :: Tuple{UnitRange}
       end
view(A, idx) = 1:1
view(A, idx) = 2:3
view(A, idx) = 4:4
view(A, idx) = 5:6

julia> [idx.block.n[1] for idx in SubBlockIterator(subblock_lasts, block_lasts)]
4-element Vector{Int64}:
 1
 2
 3
 3

julia> [idx.indices[1] for idx in SubBlockIterator(subblock_lasts, block_lasts)]
4-element Vector{UnitRange{Int64}}:
 1:1
 1:2
 1:1
 2:3
```
"""
struct SubBlockIterator
    subblock_lasts::Vector{Int}
    block_lasts::Vector{Int}
end

Base.IteratorEltype(::Type{<:SubBlockIterator}) = Base.HasEltype()
Base.eltype(::Type{<:SubBlockIterator}) = BlockIndexRange{1,Tuple{UnitRange{Int64}}}

Base.IteratorSize(::Type{<:SubBlockIterator}) = Base.HasLength()
Base.length(it::SubBlockIterator) = length(it.block_lasts)

SubBlockIterator(arr::AbstractArray, bs::NTuple{N,AbstractUnitRange{Int}}, dim::Integer) where N =
    SubBlockIterator(blocklasts(axes(arr, dim)), blocklasts(bs[dim]))

function Base.iterate(it::SubBlockIterator, state=nothing)
    if state === nothing
        i,j = 1,1
    else
        i, j = state
    end
    length(it.block_lasts)+1 == i && return nothing
    idx = i == 1 ? (1:it.block_lasts[i]) : (it.block_lasts[i-1]+1:it.block_lasts[i])

    bir = Block(j)[j == 1 ? idx : idx .- it.subblock_lasts[j-1]]
    if it.subblock_lasts[j] == it.block_lasts[i]
        j += 1
    end
    return (bir, (i + 1, j))
end

subblocks(::Any, bs::NTuple{N,AbstractUnitRange{Int}}, dim::Integer) where N =
    (nothing for _ in blockaxes(bs[dim], 1))

function subblocks(arr::AbstractArray, bs::NTuple{N,AbstractUnitRange{Int}}, dim::Integer) where N
    if size(arr, dim) == 1
        return (BlockIndexRange(Block(1), 1:1) for _ in blockaxes(bs[dim], 1))
    end
    return SubBlockIterator(arr, bs, dim)
end

@inline _bview(arg, ::Vararg) = arg
@inline _bview(A::AbstractArray, I...) = view(A, I...)

@inline function Base.Broadcast.materialize!(dest, bc::Broadcasted{BS}) where {Style,BS<:AbstractBlockStyle}
    return copyto!(dest, Base.Broadcast.instantiate(Base.Broadcast.Broadcasted{BS}(bc.f, bc.args, combine_blockaxes.(axes(dest),axes(bc)))))
end

@generated function _generic_blockbroadcast_copyto!(dest::AbstractArray,
                            bc::Broadcasted{<:AbstractBlockStyle{NDims}, <:Any, <:Any, Args}) where {NDims, Args <: Tuple}

    NArgs = length(Args.parameters)

    # `bvar(0, dim)` is a variable for BlockIndexRange of `dim`-th dimension
    # of `dest` array.  `bvar(i, dim)` is a similar variable of `i`-th
    # argument in `bc.args`.
    bvar(i, dim) = Symbol("blockindexrange_", i, "_", dim)

    function forloop(dim)
        if dim > 0
            quote
                for ($(bvar(0, dim)), $(bvar.(1:NArgs, dim)...),) in zip(
                        subblocks(dest, bs, $dim),
                        subblocks.(bc.args, Ref(bs), Ref($dim))...)
                    $(forloop(dim - 1))
                end
            end
        else
            bview(a, i) = :(_bview($a, $([bvar(i, d) for d in 1:NDims]...)))
            destview = bview(:dest, 0)
            argblocks = [bview(:(bc.args[$i]), i) for i in 1:NArgs]
            quote
                broadcast!(bc.f, $destview, $(argblocks...))
            end
        end
    end

    quote
        bs = axes(bc)
        if !blockisequal(axes(dest), bs)
            copyto!(PseudoBlockArray(dest, bs), bc)
            return dest
        end

        $(forloop(NDims))
        return dest
    end
end

copyto!(dest::AbstractArray,
        bc::Broadcasted{<:AbstractBlockStyle{NDims}, <:Any, <:Any, Args}) where {NDims, Args <: Tuple} =
    _generic_blockbroadcast_copyto!(dest, bc)

# type-stable version of _bview.(args, K)
__bview(args::Tuple{}, K) = ()
__bview(args::Tuple, K) = tuple(_bview(args[1],K), __bview(tail(args), K)...)

function _fast_blockbradcast_copyto!(dest, bc)
    @inbounds for K in blockaxes(bc)[1]
        broadcast!(bc.f, view(dest,K), __bview(bc.args, K)...)
    end
    dest
end

_hasscalarlikevec() = false
_hasscalarlikevec(a, b...) = _hasscalarlikevec(b...)
_hasscalarlikevec(a::AbstractVector, b...) = size(a,1) == 1 || _hasscalarlikevec(b...)

blockisequalorscalar(ax, ::Number) = true
blockisequalorscalar(ax, a) = blockisequal(ax, Base.axes1(a))

function copyto!(dest::AbstractVector,
        bc::Broadcasted{<:AbstractBlockStyle{1}, <:Any, <:Any, Args}) where {Args <: Tuple}
    _hasscalarlikevec(bc.args...) && return _generic_blockbroadcast_copyto!(dest, bc)
    ax = axes(dest,1)
    for a in bc.args
        blockisequalorscalar(ax, a) || return _generic_blockbroadcast_copyto!(dest, bc)
    end
    return _fast_blockbradcast_copyto!(dest, bc)
end
@inline function Broadcast.instantiate(bc::Broadcasted{Style}) where {Style <:BlockStyle}
    bcf = Broadcast.instantiate(Broadcast.flatten(Broadcasted{Nothing}(bc.f, bc.args, bc.axes)))
    return Broadcasted{Style}(bcf.f, bcf.args, bcf.axes)
end

_removeblocks(a::Broadcasted) = broadcasted(a.f, map(_removeblocks,a.args)...)
_removeblocks(a::PseudoBlockArray) = a.blocks
_removeblocks(a::BlockSlice) = a.indices
_removeblocks(a::Adjoint) = _removeblocks(parent(a))'
_removeblocks(a::Transpose) = transpose(_removeblocks(parent(a)))
_removeblocks(a::SubArray{<:Any,N,<:PseudoBlockArray}) where N = view(_removeblocks(parent(a)), map(_removeblocks, parentindices(a))...)
_removeblocks(a) = a
copy(bc::Broadcasted{PseudoBlockStyle{N}}) where N = PseudoBlockArray(materialize(_removeblocks(bc)), axes(bc))

for op in (:+, :-, :*)
    @eval function copy(bc::Broadcasted{BlockStyle{N},<:Any,typeof($op),<:Tuple{<:AbstractArray{<:Number,N}}}) where N
        (A,) = bc.args
        _BlockArray(broadcast(a -> broadcast($op, a), blocks(A)), axes(A))
    end
end

for op in (:+, :-, :*, :/, :\)
    @eval begin
        function copy(bc::Broadcasted{BlockStyle{N},<:Any,typeof($op),<:Tuple{<:Number,<:AbstractArray{<:Number,N}}}) where N
            x,A = bc.args
            _BlockArray(broadcast((x,a) -> broadcast($op, x, a), x, blocks(A)), axes(A))
        end
        function copy(bc::Broadcasted{BlockStyle{N},<:Any,typeof($op),<:Tuple{<:AbstractArray{<:Number,N},<:Number}}) where N
            A,x = bc.args
            _BlockArray(broadcast((a,x) -> broadcast($op, a, x), blocks(A),x), axes(A))
        end
    end
end

# exploit special cases for *, for example, *(::Number, ::Diagonal)
for op in (:*, :/)
    @eval @inline $op(A::BlockArray, x::Number) = _BlockArray($op(blocks(A),x), axes(A))
end
for op in (:*, :\)
    @eval @inline $op(x::Number, A::BlockArray) = _BlockArray($op(x,blocks(A)), axes(A))
end

###
# SubViews
###

_blocktype(::Type{<:BlockArray{<:Any,N,<:AbstractArray{R,N}}}) where {N,R} = R

BroadcastStyle(::Type{<:SubArray{T,N,Arr,<:NTuple{N,BlockSlice1},false}}) where {T,N,Arr<:BlockArray} =
    BroadcastStyle(_blocktype(Arr))

# special cases for SubArrays which we want to broadcast by Block
BroadcastStyle(::Type{<:SubArray{<:Any,N,<:Any,I}}) where {N,I<:Tuple{BlockSlice{<:Any,<:BlockedUnitRange},Vararg{Any}}} = BlockStyle{N}()
BroadcastStyle(::Type{<:SubArray{<:Any,N,<:Any,I}}) where {N,I<:Tuple{BlockSlice{<:Any,<:BlockedUnitRange},BlockSlice{<:Any,<:BlockedUnitRange},Vararg{Any}}} = BlockStyle{N}()
BroadcastStyle(::Type{<:SubArray{<:Any,N,<:Any,I}}) where {N,I<:Tuple{Any,BlockSlice{<:Any,<:BlockedUnitRange},Vararg{Any}}} = BlockStyle{N}()

BroadcastStyle(::Type{<:SubArray{<:Any,N,<:PseudoBlockArray,I}}) where {N,I<:Tuple{BlockSlice{<:Any,<:BlockedUnitRange},Vararg{Any}}} = PseudoBlockStyle{N}()
BroadcastStyle(::Type{<:SubArray{<:Any,N,<:PseudoBlockArray,I}}) where {N,I<:Tuple{BlockSlice{<:Any,<:BlockedUnitRange},BlockSlice{<:Any,<:BlockedUnitRange},Vararg{Any}}} = PseudoBlockStyle{N}()
BroadcastStyle(::Type{<:SubArray{<:Any,N,<:PseudoBlockArray,I}}) where {N,I<:Tuple{Any,BlockSlice{<:Any,<:BlockedUnitRange},Vararg{Any}}} = PseudoBlockStyle{N}()

###
# Fill
###

for op in (:*, :/)
    @eval begin
        broadcasted(::AbstractBlockStyle, ::typeof($op), a::Zeros, b::AbstractArray) = FillArrays._broadcasted_zeros(a, b)
        broadcasted(::AbstractBlockStyle, ::typeof($op), a::Ones{T}, b::AbstractArray{V}) where {T,V} = LinearAlgebra.copy_oftype(b, Base.promote_op(*, T, V))
    end
end

for op in (:*, :\)
    @eval begin
        broadcasted(::AbstractBlockStyle, ::typeof($op), a::AbstractArray, b::Zeros) = FillArrays._broadcasted_zeros(a, b)
        broadcasted(::AbstractBlockStyle, ::typeof($op), a::AbstractArray{T}, b::Ones{V}) where {T,V} = LinearAlgebra.copy_oftype(a, Base.promote_op(*, T, V))
    end
end