text:
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
#
#   Tests related to the text backend.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# These structures must not be defined inside a @testset. Otherwise, the test
# will fail for Julia 1.0.
struct MyColumnTable{T <: AbstractMatrix}
    names::Vector{Symbol}
    lookup::Dict{Symbol, Int}
    matrix::T
end

struct MyRowTable{T <: AbstractMatrix}
    names::Vector{Symbol}
    lookup::Dict{Symbol, Int}
    matrix::T
end

struct MyMatrixRow{T} <: Tables.AbstractRow
    row::Int
    source::MyRowTable{T}
end

data = Any[1    false      1.0     0x01 ;
           2     true      2.0     0x02 ;
           3    false      3.0     0x03 ;
           4     true      4.0     0x04 ;
           5    false      5.0     0x05 ;
           6     true      6.0     0x06 ;]

# Default
# ==============================================================================

@testset "Default" begin

    expected = """
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│      1 │  false │    1.0 │      1 │
│      2 │   true │    2.0 │      2 │
│      3 │  false │    3.0 │      3 │
│      4 │   true │    4.0 │      4 │
│      5 │  false │    5.0 │      5 │
│      6 │   true │    6.0 │      6 │
└────────┴────────┴────────┴────────┘
"""
    result = pretty_table(String, data)
    @test result == expected
end

# Alignments
# ==============================================================================

@testset "Alignments" begin
    # Left
    # ==========================================================================
    expected = """
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│ 1      │ false  │ 1.0    │ 1      │
│ 2      │ true   │ 2.0    │ 2      │
│ 3      │ false  │ 3.0    │ 3      │
│ 4      │ true   │ 4.0    │ 4      │
│ 5      │ false  │ 5.0    │ 5      │
│ 6      │ true   │ 6.0    │ 6      │
└────────┴────────┴────────┴────────┘
"""
    result = pretty_table(String, data; alignment = :l)
    @test result == expected

    # Center
    # ==========================================================================
    expected = """
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│   1    │ false  │  1.0   │   1    │
│   2    │  true  │  2.0   │   2    │
│   3    │ false  │  3.0   │   3    │
│   4    │  true  │  4.0   │   4    │
│   5    │ false  │  5.0   │   5    │
│   6    │  true  │  6.0   │   6    │
└────────┴────────┴────────┴────────┘
"""
    result = pretty_table(String, data; alignment = :c)
    @test result == expected

    # Per column configuration
    # ==========================================================================

    expected = """
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│ 1      │  false │  1.0   │      1 │
│ 2      │   true │  2.0   │      2 │
│ 3      │  false │  3.0   │      3 │
│ 4      │   true │  4.0   │      4 │
│ 5      │  false │  5.0   │      5 │
│ 6      │   true │  6.0   │      6 │
└────────┴────────┴────────┴────────┘
"""
    result = pretty_table(String, data; alignment = [:l,:r,:c,:r])
    @test result == expected

    # Cell override
    # ==========================================================================

    expected = """
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│ 1      │  false │  1.0   │ 1      │
│ 2      │   true │  2.0   │      2 │
│      3 │ false  │  3.0   │   3    │
│ 4      │   true │  4.0   │   4    │
│ 5      │  false │  5.0   │      5 │
│ 6      │   true │  6.0   │ 6      │
└────────┴────────┴────────┴────────┘
"""
    result = pretty_table(String, data;
                          alignment = [:l,:r,:c,:r],
                          cell_alignment = Dict( (3,1) => :r,
                                                 (3,2) => :l,
                                                 (1,4) => :l,
                                                 (3,4) => :c,
                                                 (4,4) => :c,
                                                 (6,4) => :l ))
    @test result == expected

    # Headers
    # ==========================================================================

    header = ["A" "B" "C" "D"
              "a" "b" "c" "d"]

    expected = """
┌───────────┬───────────┬───────────┬───────────┐
│ A         │     B     │         C │         D │
│ a         │     b     │         c │         d │
├───────────┼───────────┼───────────┼───────────┤
│ 1         │     false │    1.0    │ 1         │
│ 2         │      true │    2.0    │         2 │
│         3 │ false     │    3.0    │     3     │
│ 4         │      true │    4.0    │     4     │
│ 5         │     false │    5.0    │         5 │
│ 6         │      true │    6.0    │ 6         │
└───────────┴───────────┴───────────┴───────────┘
"""

    result = pretty_table(String, data, header;
                          alignment = [:l,:r,:c,:r],
                          cell_alignment = Dict( (3,1) => :r,
                                                 (3,2) => :l,
                                                 (1,4) => :l,
                                                 (3,4) => :c,
                                                 (4,4) => :c,
                                                 (6,4) => :l ),
                          columns_width = 9,
                          header_alignment = [:l,:c,:r,:r])
    @test result == expected

    expected = """
┌───────────┬───────────┬───────────┬───────────┐
│         A │     B     │         C │         D │
│ a         │ b         │         c │     d     │
├───────────┼───────────┼───────────┼───────────┤
│ 1         │     false │    1.0    │ 1         │
│ 2         │      true │    2.0    │         2 │
│         3 │ false     │    3.0    │     3     │
│ 4         │      true │    4.0    │     4     │
│ 5         │     false │    5.0    │         5 │
│ 6         │      true │    6.0    │ 6         │
└───────────┴───────────┴───────────┴───────────┘
"""

    result = pretty_table(String, data, header;
                          alignment = [:l,:r,:c,:r],
                          cell_alignment = Dict( (3,1) => :r,
                                                 (3,2) => :l,
                                                 (1,4) => :l,
                                                 (3,4) => :c,
                                                 (4,4) => :c,
                                                 (6,4) => :l ),
                          columns_width = 9,
                          header_alignment = [:l,:c,:r,:r],
                          header_cell_alignment = Dict( (1,1) => :r,
                                                        (2,2) => :l,
                                                        (2,4) => :c))
    @test result == expected
end

# Filters
# ==============================================================================

@testset "Filters" begin
    expected = """
┌─────┬────────┬────────┐
│ Row │ Col. 1 │ Col. 3 │
├─────┼────────┼────────┤
│   2 │      2 │    2.0 │
│   4 │      4 │    4.0 │
│   6 │      6 │    6.0 │
└─────┴────────┴────────┘
"""

    result = pretty_table(String, data;
                          filters_row     = ( (data,i) -> i%2 == 0,),
                          filters_col     = ( (data,i) -> i%2 == 1,),
                          formatters      = ft_printf("%.1f",3),
                          show_row_number = true)
    @test result == expected

    expected = """
┌─────┬────────┬────────┐
│ Row │ Col. 1 │ Col. 3 │
├─────┼────────┼────────┤
│   2 │   2    │ 2.0    │
│   4 │   4    │ 4.0    │
│   6 │   6    │ 6.0    │
└─────┴────────┴────────┘
"""

    result = pretty_table(String, data;
                          filters_row     = ( (data,i) -> i%2 == 0,),
                          filters_col     = ( (data,i) -> i%2 == 1,),
                          formatters      = ft_printf("%.1f",3),
                          show_row_number = true,
                          alignment       = [:c,:l,:l,:c])
    @test result == expected
end

# Formatters
# ==============================================================================

@testset "Formatters" begin
    expected = """
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│      1 │  false │      1 │      1 │
│      0 │   true │      0 │      0 │
│      3 │  false │      3 │      3 │
│      0 │   true │      0 │      0 │
│      5 │  false │      5 │      5 │
│      0 │   true │      0 │      0 │
└────────┴────────┴────────┴────────┘
"""

    # Single formatter.
    formatter = (data,i,j) -> begin
        if j != 2
            return isodd(i) ? i : 0
        else
            return data
        end
    end
    result = pretty_table(String, data; formatters = formatter)
    @test result == expected

    # Two formatters.
    expected = """
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│      1 │  false │      1 │      1 │
│     -1 │   true │     -1 │     -1 │
│      3 │  false │      3 │      3 │
│     -1 │   true │     -1 │     -1 │
│      5 │  false │      5 │      5 │
│     -1 │   true │     -1 │     -1 │
└────────┴────────┴────────┴────────┘
"""

    f1 = (data,i,j) -> begin
        if j != 2
            return isodd(i) ? i : 0
        else
            return data
        end
    end

    f2 = (data,i,j) -> begin
        j != 2 && return data == 0 ? -1 : data
        return data
    end

    result = pretty_table(String, data; formatters = (f1,f2))
    @test result == expected
end

# Show row number
# ==============================================================================

@testset "Show row number" begin
    expected = """
┌─────┬────────┬────────┬────────┬────────┐
│ Row │ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├─────┼────────┼────────┼────────┼────────┤
│   1 │ 1      │  false │  1.0   │      1 │
│   2 │ 2      │   true │  2.0   │      2 │
│   3 │ 3      │  false │  3.0   │      3 │
│   4 │ 4      │   true │  4.0   │      4 │
│   5 │ 5      │  false │  5.0   │      5 │
│   6 │ 6      │   true │  6.0   │      6 │
└─────┴────────┴────────┴────────┴────────┘
"""
    result = pretty_table(String, data;
                          alignment       = [:l,:r,:c,:r],
                          show_row_number = true)
    @test result == expected

    expected = """
┌─────┬────────┬────────┬────────┬────────┐
│ Row │ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├─────┼────────┼────────┼────────┼────────┤
│  1  │ 1      │  false │  1.0   │      1 │
│  2  │ 2      │   true │  2.0   │      2 │
│  3  │ 3      │  false │  3.0   │      3 │
│  4  │ 4      │   true │  4.0   │      4 │
│  5  │ 5      │  false │  5.0   │      5 │
│  6  │ 6      │   true │  6.0   │      6 │
└─────┴────────┴────────┴────────┴────────┘
"""

    result = pretty_table(String, data;
                          alignment = [:l,:r,:c,:r],
                          row_number_alignment = :c,
                          show_row_number = true)
    @test result == expected
end

# Pre-defined formats
# ==============================================================================

@testset "Pre-defined formats" begin

    # ascii_dots
    # ==========================================================================
    expected = """
.....................................
: Col. 1 : Col. 2 : Col. 3 : Col. 4 :
:........:........:........:........:
:      1 :  false :    1.0 :      1 :
:      2 :   true :    2.0 :      2 :
:      3 :  false :    3.0 :      3 :
:      4 :   true :    4.0 :      4 :
:      5 :  false :    5.0 :      5 :
:      6 :   true :    6.0 :      6 :
:........:........:........:........:
"""
    result = pretty_table(String, data, tf = ascii_dots)
    @test result == expected

    # ascii_rounded
    # ==========================================================================
    expected = """
.--------.--------.--------.--------.
| Col. 1 | Col. 2 | Col. 3 | Col. 4 |
:--------+--------+--------+--------:
|      1 |  false |    1.0 |      1 |
|      2 |   true |    2.0 |      2 |
|      3 |  false |    3.0 |      3 |
|      4 |   true |    4.0 |      4 |
|      5 |  false |    5.0 |      5 |
|      6 |   true |    6.0 |      6 |
'--------'--------'--------'--------'
"""
    result = pretty_table(String, data, tf = ascii_rounded)
    @test result == expected

    # borderless
    # ==========================================================================
    expected = """
  Col. 1   Col. 2   Col. 3   Col. 4

       1    false      1.0        1
       2     true      2.0        2
       3    false      3.0        3
       4     true      4.0        4
       5    false      5.0        5
       6     true      6.0        6
"""
    result = pretty_table(String, data, tf = borderless)
    @test result == expected

    # compact
    # ==========================================================================
    expected = """
 -------- -------- -------- --------
  Col. 1   Col. 2   Col. 3   Col. 4
 -------- -------- -------- --------
       1    false      1.0        1
       2     true      2.0        2
       3    false      3.0        3
       4     true      4.0        4
       5    false      5.0        5
       6     true      6.0        6
 -------- -------- -------- --------
"""
    result = pretty_table(String, data, tf = PrettyTables.compact)
    @test result == expected

    # dataframe
    # ==========================================================================
    expected = """
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│      1 │  false │    1.0 │      1 │
│      2 │   true │    2.0 │      2 │
│      3 │  false │    3.0 │      3 │
│      4 │   true │    4.0 │      4 │
│      5 │  false │    5.0 │      5 │
│      6 │   true │    6.0 │      6 │
"""
    result = pretty_table(String, data, tf = dataframe)
    @test result == expected

    # markdown
    # ==========================================================================
    expected = """
| Col. 1 | Col. 2 | Col. 3 | Col. 4 |
|--------|--------|--------|--------|
|      1 |  false |    1.0 |      1 |
|      2 |   true |    2.0 |      2 |
|      3 |  false |    3.0 |      3 |
|      4 |   true |    4.0 |      4 |
|      5 |  false |    5.0 |      5 |
|      6 |   true |    6.0 |      6 |
"""
    result = pretty_table(String, data, tf = markdown)
    @test result == expected

    # matrix
    # ==========================================================================

    expected = """
┌                     ┐
│ 1   false   1.0   1 │
│ 2    true   2.0   2 │
│ 3   false   3.0   3 │
│ 4    true   4.0   4 │
│ 5   false   5.0   5 │
│ 6    true   6.0   6 │
└                     ┘
"""
    result = pretty_table(String, data, tf = matrix, noheader = true)
    @test result == expected

    # mysql
    # ==========================================================================
    expected = """
+--------+--------+--------+--------+
| Col. 1 | Col. 2 | Col. 3 | Col. 4 |
+--------+--------+--------+--------+
|      1 |  false |    1.0 |      1 |
|      2 |   true |    2.0 |      2 |
|      3 |  false |    3.0 |      3 |
|      4 |   true |    4.0 |      4 |
|      5 |  false |    5.0 |      5 |
|      6 |   true |    6.0 |      6 |
+--------+--------+--------+--------+
"""
    result = pretty_table(String, data, tf = mysql)
    @test result == expected

    # simple
    # ==========================================================================
    expected = """
========= ======== ======== =========
  Col. 1   Col. 2   Col. 3   Col. 4
========= ======== ======== =========
       1    false      1.0        1
       2     true      2.0        2
       3    false      3.0        3
       4     true      4.0        4
       5    false      5.0        5
       6     true      6.0        6
========= ======== ======== =========
"""
    result = pretty_table(String, data, tf = simple)
    @test result == expected

    # unicode_rounded
    # ==========================================================================
    expected = """
╭────────┬────────┬────────┬────────╮
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│      1 │  false │    1.0 │      1 │
│      2 │   true │    2.0 │      2 │
│      3 │  false │    3.0 │      3 │
│      4 │   true │    4.0 │      4 │
│      5 │  false │    5.0 │      5 │
│      6 │   true │    6.0 │      6 │
╰────────┴────────┴────────┴────────╯
"""
    result = pretty_table(String, data, tf = unicode_rounded)
    @test result == expected

    # Custom formats
    # ==========================================================================

    expected = """
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│      1 │  false │    1.0 │      1 │
│      2 │   true │    2.0 │      2 │
│      3 │  false │    3.0 │      3 │
│      4 │   true │    4.0 │      4 │
│      5 │  false │    5.0 │      5 │
│      6 │   true │    6.0 │      6 │
"""

    tf = PrettyTables.TextFormat(unicode, hlines = [:header])
    result = pretty_table(String, data, tf = tf)
    @test result == expected

    expected = """
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
│      1 │  false │    1.0 │      1 │
│      2 │   true │    2.0 │      2 │
│      3 │  false │    3.0 │      3 │
│      4 │   true │    4.0 │      4 │
│      5 │  false │    5.0 │      5 │
│      6 │   true │    6.0 │      6 │
└────────┴────────┴────────┴────────┘
"""

    tf = PrettyTables.TextFormat(unicode, hlines = [:begin,:end])
    result = pretty_table(String, data, tf = tf)
    @test result == expected
end

# Pre-defined formatters
# ==============================================================================

@testset "Pre-defined formatters" begin

    # ft_round
    # ==========================================================================
    expected = """
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│    1.0 │    0.0 │    1.0 │    1.0 │
│    2.0 │    1.0 │    2.0 │    2.0 │
│    3.0 │    0.0 │    3.0 │    3.0 │
│    4.0 │    1.0 │    4.0 │    4.0 │
│    5.0 │    0.0 │    5.0 │    5.0 │
│    6.0 │    1.0 │    6.0 │    6.0 │
└────────┴────────┴────────┴────────┘
"""
    result = pretty_table(String, data, formatters = ft_round(1))
    @test result == expected

    expected = """
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│    1.0 │  false │    1.0 │      1 │
│    2.0 │   true │    2.0 │      2 │
│    3.0 │  false │    3.0 │      3 │
│    4.0 │   true │    4.0 │      4 │
│    5.0 │  false │    5.0 │      5 │
│    6.0 │   true │    6.0 │      6 │
└────────┴────────┴────────┴────────┘
"""
    result = pretty_table(String, data, formatters = ft_round(1,[3,1]))
    @test result == expected

    # ft_printf
    # ==========================================================================

    expected = """
┌──────────┬──────────┬──────────┬──────────┐
│   Col. 1 │   Col. 2 │   Col. 3 │   Col. 4 │
├──────────┼──────────┼──────────┼──────────┤
│    1.000 │    0.000 │    1.000 │    1.000 │
│    2.000 │    1.000 │    2.000 │    2.000 │
│    3.000 │    0.000 │    3.000 │    3.000 │
│    4.000 │    1.000 │    4.000 │    4.000 │
│    5.000 │    0.000 │    5.000 │    5.000 │
│    6.000 │    1.000 │    6.000 │    6.000 │
└──────────┴──────────┴──────────┴──────────┘
"""
    result = pretty_table(String, data, formatters = ft_printf("%8.3f"))
    @test result == expected

    expected = """
┌──────────┬────────┬────────┬──────────┐
│   Col. 1 │ Col. 2 │ Col. 3 │   Col. 4 │
├──────────┼────────┼────────┼──────────┤
│    1.000 │  false │    1.0 │    1.000 │
│    2.000 │   true │    2.0 │    2.000 │
│    3.000 │  false │    3.0 │    3.000 │
│    4.000 │   true │    4.0 │    4.000 │
│    5.000 │  false │    5.0 │    5.000 │
│    6.000 │   true │    6.0 │    6.000 │
└──────────┴────────┴────────┴──────────┘
"""
    result = pretty_table(String, data, formatters = ft_printf("%8.3f",[1,4]))
    @test result == expected

    expected = """
┌──────────┬────────┬────────┬──────────┐
│   Col. 1 │ Col. 2 │ Col. 3 │   Col. 4 │
├──────────┼────────┼────────┼──────────┤
│     1.00 │  false │    1.0 │   1.0000 │
│     2.00 │   true │    2.0 │   2.0000 │
│     3.00 │  false │    3.0 │   3.0000 │
│     4.00 │   true │    4.0 │   4.0000 │
│     5.00 │  false │    5.0 │   5.0000 │
│     6.00 │   true │    6.0 │   6.0000 │
└──────────┴────────┴────────┴──────────┘
"""
    result = pretty_table(String, data,
                          formatters = ft_printf(["%8.2f","%8.4f"],[1,4]))
    @test result == expected
end

# Sub-headers
# ==============================================================================

@testset "Sub-headers" begin
    expected = """
┌───┬───────┬─────┬───┐
│ 1 │     2 │   3 │ 4 │
├───┼───────┼─────┼───┤
│ 1 │ false │ 1.0 │ 1 │
│ 2 │  true │ 2.0 │ 2 │
│ 3 │ false │ 3.0 │ 3 │
│ 4 │  true │ 4.0 │ 4 │
│ 5 │ false │ 5.0 │ 5 │
│ 6 │  true │ 6.0 │ 6 │
└───┴───────┴─────┴───┘
"""
    result = pretty_table(String, data, [1;2;3;4])
    @test result == expected

    result = pretty_table(String, data, [1 2 3 4])
    @test result == expected

    expected = """
┌───┬───────┬─────┬───┐
│ 1 │     2 │   3 │ 4 │
│ 5 │     6 │   7 │ 8 │
├───┼───────┼─────┼───┤
│ 1 │ false │ 1.0 │ 1 │
│ 2 │  true │ 2.0 │ 2 │
│ 3 │ false │ 3.0 │ 3 │
│ 4 │  true │ 4.0 │ 4 │
│ 5 │ false │ 5.0 │ 5 │
│ 6 │  true │ 6.0 │ 6 │
└───┴───────┴─────┴───┘
"""
    result = pretty_table(String, data, [1 2 3 4; 5 6 7 8])
    @test result == expected
end

# Horizontal lines
# ==============================================================================

@testset "Horizontal lines" begin
    expected = """
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│      1 │  false │    1.0 │      1 │
│      2 │   true │    2.0 │      2 │
├────────┼────────┼────────┼────────┤
│      3 │  false │    3.0 │      3 │
│      4 │   true │    4.0 │      4 │
├────────┼────────┼────────┼────────┤
│      5 │  false │    5.0 │      5 │
│      6 │   true │    6.0 │      6 │
└────────┴────────┴────────┴────────┘
"""
    result = pretty_table(String, data;
                          body_hlines = vcat(findall(x->x == true, data[:,2])))
    @test result == expected

    expected = """
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│      1 │  false │    1.0 │      1 │
│      2 │   true │    2.0 │      2 │
├........+........+........+........┤
│      3 │  false │    3.0 │      3 │
│      4 │   true │    4.0 │      4 │
├........+........+........+........┤
│      5 │  false │    5.0 │      5 │
│      6 │   true │    6.0 │      6 │
└────────┴────────┴────────┴────────┘
"""
    result = pretty_table(String, data;
                          body_hlines = vcat(findall(x->x == true, data[:,2])),
                          body_hlines_format = ('├','+','┤','.'))
    @test result == expected

    expected = """
┌───┬───────┬─────┬───┐
│ 1 │ false │ 1.0 │ 1 │
│ 2 │  true │ 2.0 │ 2 │
├───┼───────┼─────┼───┤
│ 3 │ false │ 3.0 │ 3 │
│ 4 │  true │ 4.0 │ 4 │
├───┼───────┼─────┼───┤
│ 5 │ false │ 5.0 │ 5 │
│ 6 │  true │ 6.0 │ 6 │
└───┴───────┴─────┴───┘
"""
    result = pretty_table(String, data;
                          noheader = true,
                          body_hlines = vcat(findall(x->x == true, data[:,2])))
    @test result == expected
end

# Line breaks inside cells
# ==============================================================================

@testset "Line breaks inside cells" begin
    data = ["This line contains\nthe velocity [m/s]" 10.0;
            "This line contains\nthe acceleration [m/s^2]" 1.0;
            "This line contains\nthe time from the\nbeginning of the simulation" 10;]

    header = ["Information", "Value"]

    expected = """
┌─────────────────────────────┬───────┐
│                 Information │ Value │
├─────────────────────────────┼───────┤
│          This line contains │  10.0 │
│          the velocity [m/s] │       │
│          This line contains │   1.0 │
│    the acceleration [m/s^2] │       │
│          This line contains │    10 │
│           the time from the │       │
│ beginning of the simulation │       │
└─────────────────────────────┴───────┘
"""

    result = pretty_table(String, data, header; linebreaks = true)
    @test result == expected

    expected = """
┌─────────────────────────────┬───────┐
│         Information         │ Value │
├─────────────────────────────┼───────┤
│     This line contains      │ 10.0  │
│     the velocity [m/s]      │       │
│     This line contains      │  1.0  │
│  the acceleration [m/s^2]   │       │
│     This line contains      │  10   │
│      the time from the      │       │
│ beginning of the simulation │       │
└─────────────────────────────┴───────┘
"""

    result = pretty_table(String, data, header; alignment = :c, linebreaks = true)
    @test result == expected

    expected = """
┌─────────────────────────────┬───────┐
│ Information                 │ Value │
├─────────────────────────────┼───────┤
│ This line contains          │ 10.0  │
│ the velocity [m/s]          │       │
│ This line contains          │ 1.0   │
│ the acceleration [m/s^2]    │       │
│ This line contains          │ 10    │
│ the time from the           │       │
│ beginning of the simulation │       │
└─────────────────────────────┴───────┘
"""

    result = pretty_table(String, data, header; alignment = :l, linebreaks = true)
    @test result == expected

    expected = """
┌────────────────────────────────────────────────────────────────────┬───────┐
│                                                        Information │ Value │
├────────────────────────────────────────────────────────────────────┼───────┤
│                             This line contains\\nthe velocity [m/s] │  10.0 │
│                       This line contains\\nthe acceleration [m/s^2] │   1.0 │
│ This line contains\\nthe time from the\\nbeginning of the simulation │    10 │
└────────────────────────────────────────────────────────────────────┴───────┘
"""

    result = pretty_table(String, data, header)
    @test result == expected

    # Show only the first line
    # --------------------------------------------------------------------------

    expected = """
┌────────────────────┬───────┐
│        Information │ Value │
├────────────────────┼───────┤
│ This line contains │  10.0 │
│ This line contains │   1.0 │
│ This line contains │    10 │
└────────────────────┴───────┘
"""

    result = pretty_table(String, data, header, cell_first_line_only = true)
    @test result == expected
end

# Hiding header and sub-header
# ==============================================================================

@testset "Hide header and sub-header" begin

    # Header
    # --------------------------------------------------------------------------

    expected = """
┌───┬───────┬─────┬───┐
│ 1 │ false │ 1.0 │ 1 │
│ 2 │  true │ 2.0 │ 2 │
│ 3 │ false │ 3.0 │ 3 │
│ 4 │  true │ 4.0 │ 4 │
│ 5 │ false │ 5.0 │ 5 │
│ 6 │  true │ 6.0 │ 6 │
└───┴───────┴─────┴───┘
"""

    result = pretty_table(String, data; noheader = true)
    @test result == expected

    result = pretty_table(String, data, [1 2]; noheader = true)
    @test result == expected

    expected = """
┌───┬───────┬─────┬───┐
│ 1 │ false │ 1.0 │ 1 │
│ 2 │ true  │ 2.0 │ 2 │
│ 3 │ false │ 3.0 │ 3 │
│ 4 │ true  │ 4.0 │ 4 │
│ 5 │ false │ 5.0 │ 5 │
│ 6 │ true  │ 6.0 │ 6 │
└───┴───────┴─────┴───┘
"""
    result = pretty_table(String, data, [1 2]; alignment = :l, noheader = true)
    @test result == expected

    # Sub-header
    # --------------------------------------------------------------------------

    expected = """
┌───┬───────┬─────┬───┐
│ 1 │     2 │   3 │ 4 │
├───┼───────┼─────┼───┤
│ 1 │ false │ 1.0 │ 1 │
│ 2 │  true │ 2.0 │ 2 │
│ 3 │ false │ 3.0 │ 3 │
│ 4 │  true │ 4.0 │ 4 │
│ 5 │ false │ 5.0 │ 5 │
│ 6 │  true │ 6.0 │ 6 │
└───┴───────┴─────┴───┘
"""

    header = [1 2 3 4; "this is" "a very very" "big" "sub-header"]
    result = pretty_table(String, data, header; nosubheader = true)
    @test result == expected
end

# Print vectors
# ==============================================================================

@testset "Print vectors" begin

    vec = 0:1:10

    expected = """
┌────────┐
│ Col. 1 │
├────────┤
│      0 │
│      1 │
│      2 │
│      3 │
│      4 │
│      5 │
│      6 │
│      7 │
│      8 │
│      9 │
│     10 │
└────────┘
"""

    result = pretty_table(String, vec)
    @test result == expected

    expected = """
┌─────┬────────┐
│ Row │ Col. 1 │
├─────┼────────┤
│   1 │   0    │
│   2 │   1    │
│   3 │   2    │
│   4 │   3    │
│   5 │   4    │
│   6 │   5    │
│   7 │   6    │
│   8 │   7    │
│   9 │   8    │
│  10 │   9    │
│  11 │   10   │
└─────┴────────┘
"""

    result = pretty_table(String, vec; alignment = :c, show_row_number = true)
    @test result == expected

    expected = """
┌────────────┐
│     Header │
│ Sub-header │
├────────────┤
│          0 │
│          1 │
│          2 │
│          3 │
│          4 │
│          5 │
│          6 │
│          7 │
│          8 │
│          9 │
│         10 │
└────────────┘
"""

    result = pretty_table(String, vec, ["Header", "Sub-header"])
    @test result == expected

    @test_throws Exception pretty_table(vec, ["1" "1"])
end

# Cropping
# ==============================================================================

@testset "Cropping" begin
    expected = """
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│      1 │  false │    1.0 │      1 │
│      2 │   true │    2.0 │      2 │
│      3 │  false │    3.0 │      3 │
│      4 │   true │    4.0 │      4 │
│      5 │  false │    5.0 │      5 │
│      6 │   true │    6.0 │      6 │
└────────┴────────┴────────┴────────┘
"""

    result = pretty_table(String, data, screen_size = (10,10), crop = :none)
    @test result == expected

    result = pretty_table(String, data, screen_size = (-1,-1), crop = :both)
    @test result == expected

    expected = """
┌────────┬────────┬────────┬ ⋯
│ Col. 1 │ Col. 2 │ Col. 3 │ ⋯
├────────┼────────┼────────┼ ⋯
│      1 │  false │    1.0 │ ⋯
│      2 │   true │    2.0 │ ⋯
│   ⋮    │   ⋮    │   ⋮    │ ⋯
└────────┴────────┴────────┴ ⋯
"""

    result = pretty_table(String, data, screen_size = (10,30), crop = :both)
    @test result == expected

    result = pretty_table(String, data, screen_size = (10,30))
    @test result == expected

    expected = """
┌────────┬────────┬────────┬ ⋯
│ Col. 1 │ Col. 2 │ Col. 3 │ ⋯
├────────┼────────┼────────┼ ⋯
│      1 │  false │    1.0 │ ⋯
│      2 │   true │    2.0 │ ⋯
│      3 │  false │    3.0 │ ⋯
│      4 │   true │    4.0 │ ⋯
│      5 │  false │    5.0 │ ⋯
│      6 │   true │    6.0 │ ⋯
└────────┴────────┴────────┴ ⋯
"""

    result = pretty_table(String, data, screen_size = (10,30), crop = :horizontal)
    @test result == expected

    result = pretty_table(String, data, screen_size = (-1,30), crop = :both)
    @test result == expected

    expected = """
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│      1 │  false │    1.0 │      1 │
│      2 │   true │    2.0 │      2 │
│   ⋮    │   ⋮    │   ⋮    │   ⋮    │
└────────┴────────┴────────┴────────┘
"""

    result = pretty_table(String, data, screen_size = (10,30), crop = :vertical)
    @test result == expected

    result = pretty_table(String, data, screen_size = (10,-1), crop = :both)
    @test result == expected

    model = [1.123456789 for i = 1:8, j = 1:10]

    expected = """
┌───┬─────────────┬────┬─────┬──────┬───────┬────────┬─────────┬──────────┬───────────┐
│ … │ Col. 2      │ C… │ Co… │ Col… │ Col.… │ Col. 7 │ Col. 8  │  Col. 9  │   Col. 10 │
├───┼─────────────┼────┼─────┼──────┼───────┼────────┼─────────┼──────────┼───────────┤
│ … │ 1.123456789 │ 1… │ 1.… │ 1.1… │ 1.12… │ 1.123… │ 1.1234… │ 1.12345… │ 1.123456… │
│ … │ 1.123456789 │ 1… │ 1.… │ 1.1… │ 1.12… │ 1.123… │ 1.1234… │ 1.12345… │ 1.123456… │
│ … │ 1.123456789 │ 1… │ 1.… │ 1.1… │ 1.12… │ 1.123… │ 1.1234… │ 1.12345… │ 1.123456… │
│ … │ 1.123456789 │ 1… │ 1.… │ 1.1… │ 1.12… │ 1.123… │ 1.1234… │ 1.12345… │ 1.123456… │
│ … │ 1.123456789 │ 1… │ 1.… │ 1.1… │ 1.12… │ 1.123… │ 1.1234… │ 1.12345… │ 1.123456… │
│ … │ 1.123456789 │ 1… │ 1.… │ 1.1… │ 1.12… │ 1.123… │ 1.1234… │ 1.12345… │ 1.123456… │
│ … │ 1.123456789 │ 1… │ 1.… │ 1.1… │ 1.12… │ 1.123… │ 1.1234… │ 1.12345… │ 1.123456… │
│ … │ 1.123456789 │ 1… │ 1.… │ 1.1… │ 1.12… │ 1.123… │ 1.1234… │ 1.12345… │ 1.123456… │
└───┴─────────────┴────┴─────┴──────┴───────┴────────┴─────────┴──────────┴───────────┘
"""

    result = pretty_table(String, model,
                          columns_width = [1, 0, 2, 3, 4, 5, 6, 7, 8, 9],
                          alignment = [:l, :l, :r, :c, :r, :c, :l, :l, :c, :r])

    expected = """
┌────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │ Col. 5 │ Col. 6 │ Col. 7 │ Col. 8 │ Col. 9 │ Col. … │
├────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┤
│ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │
│ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │
│ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │
│ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │
│ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │
│ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │
│ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │
│ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │ 1.123… │
└────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────┘
"""

    result = pretty_table(String, model,
                          columns_width = 6,
                          alignment = [:l, :l, :r, :c, :r, :c, :l, :l, :c, :r])

    @test result == expected

    # Sub-header cropping
    # --------------------------------------------------------------------------

    header = ["A"            "B"             "C"            "D"
              "First column" "Second column" "Third column" "Fourth column"]

    expected = """
┌───┬───────┬─────┬───┐
│ A │     B │   C │ D │
│ … │ Seco… │ Th… │ … │
├───┼───────┼─────┼───┤
│ 1 │ false │ 1.0 │ 1 │
│ 2 │  true │ 2.0 │ 2 │
│ 3 │ false │ 3.0 │ 3 │
│ 4 │  true │ 4.0 │ 4 │
│ 5 │ false │ 5.0 │ 5 │
│ 6 │  true │ 6.0 │ 6 │
└───┴───────┴─────┴───┘
"""

    result = pretty_table(String, data, header, crop_subheader = true)

    @test result == expected
end

# Minimum and maximum column width
# ==============================================================================

@testset "Minimum and maximum column width" begin
    header = ["A" "B" "C" "D"]

    # Minimum column width
    # ==========================================================================

    expected = """
┌──────┬───────┬──────┬──────┐
│    A │     B │    C │    D │
├──────┼───────┼──────┼──────┤
│    1 │ false │  1.0 │    1 │
│    2 │  true │  2.0 │    2 │
│    3 │ false │  3.0 │    3 │
│    4 │  true │  4.0 │    4 │
│    5 │ false │  5.0 │    5 │
│    6 │  true │  6.0 │    6 │
└──────┴───────┴──────┴──────┘
"""

    result = pretty_table(String, data, header, minimum_columns_width = 4)
    @test result == expected

    # Precedence of `columns_width`.

    expected = """
┌────┬────┬────┬────┐
│  A │  B │  C │  D │
├────┼────┼────┼────┤
│  1 │ f… │ 1… │  1 │
│  2 │ t… │ 2… │  2 │
│  3 │ f… │ 3… │  3 │
│  4 │ t… │ 4… │  4 │
│  5 │ f… │ 5… │  5 │
│  6 │ t… │ 6… │  6 │
└────┴────┴────┴────┘
"""

    result = pretty_table(String, data, header,
                          columns_width = 2,
                          minimum_columns_width = 4)
    @test result == expected

    # Test with a vector in `minimum_column_width`.

    expected = """
┌────┬───────┬────────┬──────────┐
│  A │     B │      C │        D │
├────┼───────┼────────┼──────────┤
│  1 │ false │    1.0 │        1 │
│  2 │  true │    2.0 │        2 │
│  3 │ false │    3.0 │        3 │
│  4 │  true │    4.0 │        4 │
│  5 │ false │    5.0 │        5 │
│  6 │  true │    6.0 │        6 │
└────┴───────┴────────┴──────────┘
"""

    result = pretty_table(String, data, header,
                          minimum_columns_width = [2,4,6,8])
    @test result == expected

    # Test with the option `equal_columns_width``.

    expected = """
┌──────────┬──────────┬──────────┬──────────┐
│        A │        B │        C │        D │
├──────────┼──────────┼──────────┼──────────┤
│        1 │    false │      1.0 │        1 │
│        2 │     true │      2.0 │        2 │
│        3 │    false │      3.0 │        3 │
│        4 │     true │      4.0 │        4 │
│        5 │    false │      5.0 │        5 │
│        6 │     true │      6.0 │        6 │
└──────────┴──────────┴──────────┴──────────┘
"""

    result = pretty_table(String, data, header,
                          minimum_columns_width = [2,4,6,8],
                          equal_columns_width = true)
    @test result == expected

    # Maximum column width
    # ==========================================================================

    expected = """
┌───┬─────┬─────┬───┐
│ A │   B │   C │ D │
├───┼─────┼─────┼───┤
│ 1 │ fa… │ 1.0 │ 1 │
│ 2 │ tr… │ 2.0 │ 2 │
│ 3 │ fa… │ 3.0 │ 3 │
│ 4 │ tr… │ 4.0 │ 4 │
│ 5 │ fa… │ 5.0 │ 5 │
│ 6 │ tr… │ 6.0 │ 6 │
└───┴─────┴─────┴───┘
"""

    result = pretty_table(String, data, header,
                          maximum_columns_width = 3)
    @test result == expected

    # Precedence of `columns_width`.

    expected = """
┌───┬────┬─────┬──────┐
│ A │  B │   C │    D │
├───┼────┼─────┼──────┤
│ 1 │ f… │ 1.0 │    1 │
│ 2 │ t… │ 2.0 │    2 │
│ 3 │ f… │ 3.0 │    3 │
│ 4 │ t… │ 4.0 │    4 │
│ 5 │ f… │ 5.0 │    5 │
│ 6 │ t… │ 6.0 │    6 │
└───┴────┴─────┴──────┘
"""

    result = pretty_table(String, data, header,
                          columns_width = [1,2,3,4],
                          maximum_columns_width = 3)
    @test result == expected

    # Test with a vector in `maximum_column_width`.

    expected = """
┌───┬─────┬────┬───┐
│ A │   B │  C │ D │
├───┼─────┼────┼───┤
│ 1 │ fa… │ 1… │ 1 │
│ 2 │ tr… │ 2… │ 2 │
│ 3 │ fa… │ 3… │ 3 │
│ 4 │ tr… │ 4… │ 4 │
│ 5 │ fa… │ 5… │ 5 │
│ 6 │ tr… │ 6… │ 6 │
└───┴─────┴────┴───┘
"""

    result = pretty_table(String, data, header,
                          maximum_columns_width = [20,3,2,5])
    @test result == expected

    # Test with the option `equal_columns_width``.

    expected = """
┌─────┬─────┬─────┬─────┐
│   A │   B │   C │   D │
├─────┼─────┼─────┼─────┤
│   1 │ fa… │ 1.0 │   1 │
│   2 │ tr… │ 2.0 │   2 │
│   3 │ fa… │ 3.0 │   3 │
│   4 │ tr… │ 4.0 │   4 │
│   5 │ fa… │ 5.0 │   5 │
│   6 │ tr… │ 6.0 │   6 │
└─────┴─────┴─────┴─────┘
"""

    result = pretty_table(String, data, header,
                          equal_columns_width = true,
                          maximum_columns_width = [20,3,2,5])
    @test result == expected
end

# Auto wrapping
# ==============================================================================

@testset "Auto wrapping" begin
    table = [1 """Ouviram do Ipiranga as margens plácidas
                  De um povo heróico o brado retumbante,
                  E o sol da Liberdade, em raios fúlgidos,
                  Brilhou no céu da Pátria nesse instante.""";
             2 """Se o penhor dessa igualdade
                  Conseguimos conquistar com braço forte,
                  Em teu seio, ó Liberdade,
                  Desafia o nosso peito a própria morte!""";
             3 """Ó Pátria amada, Idolatrada, Salve! Salve!
                  Brasil, um sonho intenso, um raio vívido
                  De amor e de esperança à terra desce,
                  Se em teu formoso céu, risonho e límpido,"""]

    header = ["Verse number", "Verse"]

    expected = """
┌──────────────┬────────────────────────────────┐
│ Verse number │                          Verse │
├──────────────┼────────────────────────────────┤
│            1 │         Ouviram do Ipiranga as │
│              │               margens plácidas │
│              │     De um povo heróico o brado │
│              │                    retumbante, │
│              │       E o sol da Liberdade, em │
│              │                raios fúlgidos, │
│              │       Brilhou no céu da Pátria │
│              │                nesse instante. │
├──────────────┼────────────────────────────────┤
│            2 │    Se o penhor dessa igualdade │
│              │     Conseguimos conquistar com │
│              │                   braço forte, │
│              │      Em teu seio, ó Liberdade, │
│              │        Desafia o nosso peito a │
│              │                 própria morte! │
├──────────────┼────────────────────────────────┤
│            3 │    Ó Pátria amada, Idolatrada, │
│              │                  Salve! Salve! │
│              │   Brasil, um sonho intenso, um │
│              │                    raio vívido │
│              │       De amor e de esperança à │
│              │                   terra desce, │
│              │         Se em teu formoso céu, │
│              │             risonho e límpido, │
└──────────────┴────────────────────────────────┘
"""

    result = pretty_table(String, table, header,
                          autowrap      = true,
                          linebreaks    = true,
                          body_hlines   = [1,2],
                          columns_width = [-1,30])

    @test result == expected

    expected = """
┌──────────────┬────────────────────────────────┐
│ Verse number │             Verse              │
├──────────────┼────────────────────────────────┤
│      1       │     Ouviram do Ipiranga as     │
│              │        margens plácidas        │
│              │   De um povo heróico o brado   │
│              │          retumbante,           │
│              │    E o sol da Liberdade, em    │
│              │        raios fúlgidos,         │
│              │    Brilhou no céu da Pátria    │
│              │        nesse instante.         │
├──────────────┼────────────────────────────────┤
│      2       │  Se o penhor dessa igualdade   │
│              │   Conseguimos conquistar com   │
│              │          braço forte,          │
│              │   Em teu seio, ó Liberdade,    │
│              │    Desafia o nosso peito a     │
│              │         própria morte!         │
├──────────────┼────────────────────────────────┤
│      3       │  Ó Pátria amada, Idolatrada,   │
│              │         Salve! Salve!          │
│              │  Brasil, um sonho intenso, um  │
│              │          raio vívido           │
│              │    De amor e de esperança à    │
│              │          terra desce,          │
│              │     Se em teu formoso céu,     │
│              │       risonho e límpido,       │
└──────────────┴────────────────────────────────┘
"""

    result = pretty_table(String, table, header,
                          alignment     = :c,
                          autowrap      = true,
                          linebreaks    = true,
                          body_hlines   = [1,2],
                          columns_width = [-1,30])

    @test result == expected

    expected = """
┌──────────────┬────────────────────────────────┐
│ Verse number │ Verse                          │
├──────────────┼────────────────────────────────┤
│ 1            │ Ouviram do Ipiranga as         │
│              │ margens plácidas               │
│              │ De um povo heróico o brado     │
│              │ retumbante,                    │
│              │ E o sol da Liberdade, em       │
│              │ raios fúlgidos,                │
│              │ Brilhou no céu da Pátria       │
│              │ nesse instante.                │
├──────────────┼────────────────────────────────┤
│ 2            │ Se o penhor dessa igualdade    │
│              │ Conseguimos conquistar com     │
│              │ braço forte,                   │
│              │ Em teu seio, ó Liberdade,      │
│              │ Desafia o nosso peito a        │
│              │ própria morte!                 │
├──────────────┼────────────────────────────────┤
│ 3            │ Ó Pátria amada, Idolatrada,    │
│              │ Salve! Salve!                  │
│              │ Brasil, um sonho intenso, um   │
│              │ raio vívido                    │
│              │ De amor e de esperança à       │
│              │ terra desce,                   │
│              │ Se em teu formoso céu,         │
│              │ risonho e límpido,             │
└──────────────┴────────────────────────────────┘
"""

    result = pretty_table(String, table, header,
                          alignment     = :l,
                          autowrap      = true,
                          linebreaks    = true,
                          body_hlines   = [1,2],
                          columns_width = [-1,30])

    @test result == expected

    # Test with additional rows
    # --------------------------------------------------------------------------

    expected = """
┌─────┬──────────┬──────────────┬────────────────────────────────┐
│ Row │    Verso │ Verse number │ Verse                          │
├─────┼──────────┼──────────────┼────────────────────────────────┤
│   1 │ Primeiro │ 1            │ Ouviram do Ipiranga as         │
│     │          │              │ margens plácidas               │
│     │          │              │ De um povo heróico o brado     │
│     │          │              │ retumbante,                    │
│     │          │              │ E o sol da Liberdade, em       │
│     │          │              │ raios fúlgidos,                │
│     │          │              │ Brilhou no céu da Pátria       │
│     │          │              │ nesse instante.                │
├─────┼──────────┼──────────────┼────────────────────────────────┤
│   2 │  Segundo │ 2            │ Se o penhor dessa igualdade    │
│     │          │              │ Conseguimos conquistar com     │
│     │          │              │ braço forte,                   │
│     │          │              │ Em teu seio, ó Liberdade,      │
│     │          │              │ Desafia o nosso peito a        │
│     │          │              │ própria morte!                 │
├─────┼──────────┼──────────────┼────────────────────────────────┤
│   3 │ Terceiro │ 3            │ Ó Pátria amada, Idolatrada,    │
│     │          │              │ Salve! Salve!                  │
│     │          │              │ Brasil, um sonho intenso, um   │
│     │          │              │ raio vívido                    │
│     │          │              │ De amor e de esperança à       │
│     │          │              │ terra desce,                   │
│     │          │              │ Se em teu formoso céu,         │
│     │          │              │ risonho e límpido,             │
└─────┴──────────┴──────────────┴────────────────────────────────┘
"""

    result = pretty_table(String, table, header,
                          alignment             = :l,
                          autowrap              = true,
                          linebreaks            = true,
                          body_hlines           = [1,2],
                          columns_width         = [-1,30],
                          show_row_number       = true,
                          row_names             = ["Primeiro","Segundo","Terceiro"],
                          row_name_column_title = "Verso")

    @test result == expected
end

# Dictionaries
# ==============================================================================

@testset "Dictionaries" begin
    dict = Dict{Int64,String}(1 => "Jan", 2 => "Feb", 3 => "Mar", 4 => "Apr",
                              5 => "May", 6 => "Jun")

    expected = """
┌───────┬────────┐
│  Keys │ Values │
│ Int64 │ String │
├───────┼────────┤
│     1 │    Jan │
│     2 │    Feb │
│     3 │    Mar │
│     4 │    Apr │
│     5 │    May │
│     6 │    Jun │
└───────┴────────┘
"""

    result = pretty_table(String, dict, sortkeys = true)
    @test result == expected
end

# Row names
# ==============================================================================

@testset "Show row names" begin
    expected = """
┌───────┬─────┬───────┬───────┬─────┐
│       │  C1 │    C2 │    C3 │  C4 │
│       │ Int │  Bool │ Float │ Hex │
├───────┼─────┼───────┼───────┼─────┤
│ Row 1 │   1 │ false │   1.0 │   1 │
│ Row 2 │   2 │  true │   2.0 │   2 │
│ Row 3 │   3 │ false │   3.0 │   3 │
│ Row 4 │   4 │  true │   4.0 │   4 │
│ Row 5 │   5 │ false │   5.0 │   5 │
│ Row 6 │   6 │  true │   6.0 │   6 │
└───────┴─────┴───────┴───────┴─────┘
"""

    result = pretty_table(String, data,
                          ["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"],
                          row_names = ["Row $i" for i = 1:6])

    @test result == expected

    expected = """
┌───────────┬─────┬───────┬───────┬─────┐
│ Row names │  C1 │    C2 │    C3 │  C4 │
│           │ Int │  Bool │ Float │ Hex │
├───────────┼─────┼───────┼───────┼─────┤
│     Row 1 │   1 │ false │   1.0 │   1 │
│     Row 2 │   2 │  true │   2.0 │   2 │
│     Row 3 │   3 │ false │   3.0 │   3 │
│     Row 4 │   4 │  true │   4.0 │   4 │
│     Row 5 │   5 │ false │   5.0 │   5 │
│     Row 6 │   6 │  true │   6.0 │   6 │
└───────────┴─────┴───────┴───────┴─────┘
"""

    result = pretty_table(String, data,
                          ["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"],
                          row_names = ["Row $i" for i = 1:6],
                          row_name_column_title = "Row names")

    @test result == expected

    expected = """
┌───────────┬─────┬───────┬───────┬─────┐
│ Row names │  C1 │    C2 │    C3 │  C4 │
│           │ Int │  Bool │ Float │ Hex │
├───────────┼─────┼───────┼───────┼─────┤
│   Row 1   │   1 │ false │   1.0 │   1 │
│   Row 2   │   2 │  true │   2.0 │   2 │
│   Row 3   │   3 │ false │   3.0 │   3 │
│   Row 4   │   4 │  true │   4.0 │   4 │
│   Row 5   │   5 │ false │   5.0 │   5 │
│   Row 6   │   6 │  true │   6.0 │   6 │
└───────────┴─────┴───────┴───────┴─────┘
"""

    result = pretty_table(String, data,
                          ["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"],
                          row_names = ["Row $i" for i = 1:6],
                          row_name_column_title = "Row names",
                          row_name_alignment = :c)

    @test result == expected

    expected = """
┌───────────┬─────┬───────┬───────┬─── ⋯
│ Row names │  C1 │    C2 │    C3 │  C ⋯
│           │ Int │  Bool │ Float │ He ⋯
├───────────┼─────┼───────┼───────┼─── ⋯
│   Row 1   │   1 │ false │   1.0 │    ⋯
│   Row 2   │   2 │  true │   2.0 │    ⋯
│     ⋮     │  ⋮  │   ⋮   │   ⋮   │  ⋮ ⋯
└───────────┴─────┴───────┴───────┴─── ⋯
"""

    result = pretty_table(String, data,
                          ["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"],
                          row_names = ["Row $i" for i = 1:6],
                          row_name_column_title = "Row names",
                          row_name_alignment = :c,
                          screen_size = (11,40))
    @test result == expected
end

# Vertical lines
# ==============================================================================

@testset "Vertical lines" begin
    expected = """
────────────────────────────
  C1      C2      C3    C4
 Int    Bool   Float   Hex
────────────────────────────
   1   false     1.0     1
   2    true     2.0     2
   3   false     3.0     3
   4    true     4.0     4
   5   false     5.0     5
   6    true     6.0     6
────────────────────────────
"""

    result1 = pretty_table(String, data,
                           ["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"],
                           vlines = [])
    result2 = pretty_table(String, data,
                           ["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"],
                           vlines = :none)
    @test result1 == expected
    @test result2 == expected

    expected = """
┌───────────────────────────┐
│  C1      C2      C3    C4 │
│ Int    Bool   Float   Hex │
├───────────────────────────┤
│   1   false     1.0     1 │
│   2    true     2.0     2 │
│   3   false     3.0     3 │
│   4    true     4.0     4 │
│   5   false     5.0     5 │
│   6    true     6.0     6 │
└───────────────────────────┘
"""

    result1 = pretty_table(String, data,
                           ["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"],
                           vlines = [:begin,:end])
    result2 = pretty_table(String, data,
                           ["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"],
                           vlines = [0,4])
    @test result1 == expected
    @test result2 == expected

    expected = """
┌─────┬───────────────────────────┐
│ Row │  C1      C2      C3    C4 │
│     │ Int    Bool   Float   Hex │
├─────┼───────────────────────────┤
│   1 │   1   false     1.0     1 │
│   2 │   2    true     2.0     2 │
│   3 │   3   false     3.0     3 │
│   4 │   4    true     4.0     4 │
│   5 │   5   false     5.0     5 │
│   6 │   6    true     6.0     6 │
└─────┴───────────────────────────┘
"""

    result = pretty_table(String, data,
                          ["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"],
                          vlines = [:begin,1,:end],
                          show_row_number = true)
    @test result == expected

    expected = """
┌─────┬───────────┬───────────────────────────┐
│ Row │ Row names │  C1      C2      C3    C4 │
│     │           │ Int    Bool   Float   Hex │
├─────┼───────────┼───────────────────────────┤
│   1 │   Row 1   │   1   false     1.0     1 │
│   2 │   Row 2   │   2    true     2.0     2 │
│   3 │   Row 3   │   3   false     3.0     3 │
│   4 │   Row 4   │   4    true     4.0     4 │
│   5 │   Row 5   │   5   false     5.0     5 │
│   6 │   Row 6   │   6    true     6.0     6 │
└─────┴───────────┴───────────────────────────┘
"""

    result = pretty_table(String, data,
                          ["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"],
                          vlines = [:begin,1,2,:end],
                          show_row_number = true,
                          row_names = ["Row $i" for i = 1:6],
                          row_name_column_title = "Row names",
                          row_name_alignment = :c)
    @test result == expected

    expected = """
┌─────┬───────────┬─────────────┬ ⋯
│ Row │ Row names │  C1      C2 │ ⋯
│     │           │ Int    Bool │ ⋯
├─────┼───────────┼─────────────┼ ⋯
│   1 │   Row 1   │   1   false │ ⋯
│   2 │   Row 2   │   2    true │ ⋯
│  ⋮  │     ⋮     │  ⋮      ⋮   │ ⋯
└─────┴───────────┴─────────────┴ ⋯
"""

    result = pretty_table(String, data,
                          ["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"],
                          vlines = [:begin,1,2,4,:end],
                          show_row_number = true,
                          row_names = ["Row $i" for i = 1:6],
                          row_name_column_title = "Row names",
                          row_name_alignment = :c,
                          screen_size = (11,35))
    @test result == expected
end

# Strings with characters that have screen size different than 1
# ==============================================================================

@testset "Strings with characters that have variable width" begin
    matrix = ["😄"^10 "😄"^10 "😅"^10; "🧐"^5 "🥺"^5 "😇"^5; "a"^10 "a"^10 "a"^10]
    header = ["😋"^5 "😁"^10 "🤣"^15; "⚡"^15 "👽"^10 "🤩"^5]

    expected = """
┌────────────────────────────────┬──────────────────────┬────────────────────────────────┐
│ 😋😋😋😋😋                     │ 😁😁😁😁😁😁😁😁😁😁 │ 🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣 │
│ ⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡ │ 👽👽👽👽👽👽👽👽👽👽 │                     🤩🤩🤩🤩🤩 │
├────────────────────────────────┼──────────────────────┼────────────────────────────────┤
│ 😄😄😄😄😄😄😄😄😄😄           │ 😄😄😄😄😄😄😄😄😄😄 │           😅😅😅😅😅😅😅😅😅😅 │
│ 🧐🧐🧐🧐🧐                     │      🥺🥺🥺🥺🥺      │                     😇😇😇😇😇 │
│ aaaaaaaaaa                     │      aaaaaaaaaa      │                     aaaaaaaaaa │
└────────────────────────────────┴──────────────────────┴────────────────────────────────┘
"""

    result = pretty_table(String, matrix, header, alignment = [:l, :c, :r])

    @test result == expected

    # Crop
    # ----

    expected = """
┌────────────────────────────────┬──────────────────────┬─────────────────────────────── ⋯
│ 😋😋😋😋😋                     │ 😁😁😁😁😁😁😁😁😁😁 │ 🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣 ⋯
│ ⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡ │ 👽👽👽👽👽👽👽👽👽👽 │                     🤩🤩🤩🤩🤩 ⋯
├────────────────────────────────┼──────────────────────┼─────────────────────────────── ⋯
│ 😄😄😄😄😄😄😄😄😄😄           │ 😄😄😄😄😄😄😄😄😄😄 │           😅😅😅😅😅😅😅😅😅😅 ⋯
│ 🧐🧐🧐🧐🧐                     │      🥺🥺🥺🥺🥺      │                     😇😇😇😇😇 ⋯
│ aaaaaaaaaa                     │      aaaaaaaaaa      │                     aaaaaaaaaa ⋯
└────────────────────────────────┴──────────────────────┴─────────────────────────────── ⋯
"""

    result = pretty_table(String, matrix, header,
                          alignment = [:l, :c, :r],
                          screen_size = (0,90))

    @test result == expected

    expected = """
┌────────────────────────────────┬──────────────────────┬────────────────────────────── ⋯
│ 😋😋😋😋😋                     │ 😁😁😁😁😁😁😁😁😁😁 │ 🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣  ⋯
│ ⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡ │ 👽👽👽👽👽👽👽👽👽👽 │                     🤩🤩🤩🤩  ⋯
├────────────────────────────────┼──────────────────────┼────────────────────────────── ⋯
│ 😄😄😄😄😄😄😄😄😄😄           │ 😄😄😄😄😄😄😄😄😄😄 │           😅😅😅😅😅😅😅😅😅  ⋯
│ 🧐🧐🧐🧐🧐                     │      🥺🥺🥺🥺🥺      │                     😇😇😇😇  ⋯
│ aaaaaaaaaa                     │      aaaaaaaaaa      │                     aaaaaaaaa ⋯
└────────────────────────────────┴──────────────────────┴────────────────────────────── ⋯
"""

    result = pretty_table(String, matrix, header,
                          alignment = [:l, :c, :r],
                          screen_size = (0,89))

    @test result == expected
end

# Test if we can print `missing`, `nothing`, and `#undef`
# ==============================================================================

@testset "Print missing, nothing, and #undef" begin

    matrix = Matrix{Any}(undef,3,3)
    matrix[1,1:2] .= missing
    matrix[2,1:2] .= nothing
    matrix[3,1]   = missing
    matrix[3,2]   = nothing

    expected = """
┌─────────┬─────────┬────────┐
│  Col. 1 │  Col. 2 │ Col. 3 │
├─────────┼─────────┼────────┤
│ missing │ missing │ #undef │
│ nothing │ nothing │ #undef │
│ missing │ nothing │ #undef │
└─────────┴─────────┴────────┘
"""

    result = pretty_table(String, matrix)
    @test result == expected
end

# Titles
# ==============================================================================

@testset "Titles" begin
    title = "This is a very very long title that will be displayed above the table."

    expected = """
This is a very very long title that will be displayed above the table.
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│      1 │  false │    1.0 │      1 │
│      2 │   true │    2.0 │      2 │
│      3 │  false │    3.0 │      3 │
│      4 │   true │    4.0 │      4 │
│      5 │  false │    5.0 │      5 │
│      6 │   true │    6.0 │      6 │
└────────┴────────┴────────┴────────┘
"""
    result = pretty_table(String, data,
                          title = title,
                          title_crayon = Crayon())
    @test result == expected

    expected = """
This is a very very long title that …
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│      1 │  false │    1.0 │      1 │
│      2 │   true │    2.0 │      2 │
│      3 │  false │    3.0 │      3 │
│      4 │   true │    4.0 │      4 │
│      5 │  false │    5.0 │      5 │
│      6 │   true │    6.0 │      6 │
└────────┴────────┴────────┴────────┘
"""
    result = pretty_table(String, data,
                          title = title,
                          title_crayon = Crayon(),
                          title_same_width_as_table = true)
    @test result == expected

    expected = """
This is a very very long title that
will be displayed above the table.
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│      1 │  false │    1.0 │      1 │
│      2 │   true │    2.0 │      2 │
│      3 │  false │    3.0 │      3 │
│      4 │   true │    4.0 │      4 │
│      5 │  false │    5.0 │      5 │
│      6 │   true │    6.0 │      6 │
└────────┴────────┴────────┴────────┘
"""
    result = pretty_table(String, data,
                          title = title,
                          title_autowrap = true,
                          title_crayon = Crayon(),
                          title_same_width_as_table = true)
    @test result == expected

    expected = """
 This is a very very long title that
 will be displayed above the table.
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│      1 │  false │    1.0 │      1 │
│      2 │   true │    2.0 │      2 │
│      3 │  false │    3.0 │      3 │
│      4 │   true │    4.0 │      4 │
│      5 │  false │    5.0 │      5 │
│      6 │   true │    6.0 │      6 │
└────────┴────────┴────────┴────────┘
"""
    result = pretty_table(String, data,
                          title = title,
                          title_alignment = :c,
                          title_autowrap = true,
                          title_crayon = Crayon(),
                          title_same_width_as_table = true)
    @test result == expected

    expected = """
  This is a very very long title that
   will be displayed above the table.
┌────────┬────────┬────────┬────────┐
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│      1 │  false │    1.0 │      1 │
│      2 │   true │    2.0 │      2 │
│      3 │  false │    3.0 │      3 │
│      4 │   true │    4.0 │      4 │
│      5 │  false │    5.0 │      5 │
│      6 │   true │    6.0 │      6 │
└────────┴────────┴────────┴────────┘
"""
    result = pretty_table(String, data,
                          title = title,
                          title_alignment = :r,
                          title_autowrap = true,
                          title_crayon = Crayon(),
                          title_same_width_as_table = true)
    @test result == expected
end

# Helpers
# ==============================================================================

@testset "@pt" begin
    # To get the output of the macro @pt, we must redirect the stdout.
    old_stdout = stdout
    in, out    = redirect_stdout()

    # The configurations must not interfere with the printings of any type
    # except for the `Dict`.
    @ptconf sortkeys = true

    # Test 1
    # --------------------------------------------------------------------------

    expected = """
.......................
: 1 :     2 :   3 : 4 :
:...:.......:.....:...:
: 1 : false : 1.0 : 1 :
: 2 :  true : 2.0 : 2 :
: 3 : false : 3.0 : 3 :
: 4 :  true : 4.0 : 4 :
: 5 : false : 5.0 : 5 :
: 6 :  true : 6.0 : 6 :
:...:.......:.....:...:
"""

    @ptconf tf = ascii_dots
    @pt :header = ["1","2","3","4"] data

    result = String(readavailable(in))
    @test result == expected

    # Test 2
    # --------------------------------------------------------------------------

    expected = """
╭────────┬────────┬────────┬────────╮
│ Col. 1 │ Col. 2 │ Col. 3 │ Col. 4 │
├────────┼────────┼────────┼────────┤
│   1    │ false  │  1.0   │   1    │
│   2    │  true  │  2.0   │   2    │
│   3    │ false  │  3.0   │   3    │
│   4    │  true  │  4.0   │   4    │
│   5    │ false  │  5.0   │   5    │
│   6    │  true  │  6.0   │   6    │
╰────────┴────────┴────────┴────────╯
"""

    @ptconf tf = unicode_rounded
    @ptconf alignment = :c
    @pt data

    result = String(readavailable(in))
    @test result == expected

    # Test 3
    # --------------------------------------------------------------------------

    expected = """
.----------.----------.
| Column 1 | Column 2 |
|   Sub. 1 |   Sub. 2 |
:----------+----------:
|        1 |        2 |
|        3 |        4 |
|        5 |        6 |
'----------'----------'
"""

    header = ["Column 1" "Column 2"; "Sub. 1" "Sub. 2"]

    @ptconfclean
    @ptconf tf = ascii_rounded
    @pt :header = header [1 2; 3 4; 5 6]

    result = String(readavailable(in))
    @test result == expected

    # Test 4
    # --------------------------------------------------------------------------

    expected = """
.-------.---------------------.
| Keys  | Values              |
| Int64 | String              |
:-------+---------------------:
| 1     | São José dos Campos |
| 2     | SP                  |
| 3     | Brasil              |
'-------'---------------------'
"""

    @ptconf sortkeys = true
    @ptconf alignment = :l
    @pt d = Dict(Int64(1) => "São José dos Campos",
                 Int64(2) => "SP",
                 Int64(3) => "Brasil")

    result = String(readavailable(in))
    @test result == expected

    # Restore the original stdout.
    close(in)
    close(out)
    redirect_stdout(old_stdout)

    # The expression after `@pt` must be evaluated. Hence, `d` must be a dict
    # with the defined elements.
    @test d isa Dict
    @test d[1] == "São José dos Campos"
    @test d[2] == "SP"
    @test d[3] == "Brasil"
end

# Overwrite
# ==============================================================================

@testset "Overwrite" begin
    result = pretty_table(String, data, ["A" "B" "C" "D"; "E" "F" "G" "H"],
                          body_hlines = collect(1:1:6))

    num_lines = length(findall(x->x == '\n', result))

    io = IOBuffer()
    pretty_table(io, data, ["A" "B" "C" "D"; "E" "F" "G" "H"],
                 body_hlines = collect(1:1:6),
                 overwrite = true)

    io_result = String(take!(io))

    @test io_result == ("\e[1F\e[2K"^(num_lines) * result)
end

# Table.jl compatibility
# ==============================================================================

@testset "Tables.jl compatibility" begin
    # A DataFrame is compliant with Tables.jl API.
    df = DataFrame(x = Int64(1):Int64(3),
                   y = 'a':'c',
                   z = ["String 1";"String 2";"String 3"]);

    # Thus, the following 5 calls must provide the same results.
    result_1 = pretty_table(String, df)
    result_2 = pretty_table(String, Tables.rowtable(df))
    result_3 = pretty_table(String, Tables.columntable(df))
    result_4 = pretty_table(String, Tables.columns(df))
    result_5 = pretty_table(String, Tables.rows(df))

    expected = """
┌───────┬──────┬──────────┐
│     x │    y │        z │
│ Int64 │ Char │   String │
├───────┼──────┼──────────┤
│     1 │    a │ String 1 │
│     2 │    b │ String 2 │
│     3 │    c │ String 3 │
└───────┴──────┴──────────┘
"""

    @test result_1 == result_2 == result_3 == result_4 == result_5 == expected

    # If a header is passed, then it must replace the Tables.jl schema.

    result = pretty_table(String, df, ["My col. 1", "My col. 2", "My col. 3"])

    expected = """
┌───────────┬───────────┬───────────┐
│ My col. 1 │ My col. 2 │ My col. 3 │
├───────────┼───────────┼───────────┤
│         1 │         a │  String 1 │
│         2 │         b │  String 2 │
│         3 │         c │  String 3 │
└───────────┴───────────┴───────────┘
"""

    @test result == expected

    # Test the case in which a schema is not available
    # ==========================================================================

    expected = """
┌───┬───────┬─────┬───┐
│ a │     b │   c │ d │
├───┼───────┼─────┼───┤
│ 1 │ false │ 1.0 │ 1 │
│ 2 │  true │ 2.0 │ 2 │
│ 3 │ false │ 3.0 │ 3 │
│ 4 │  true │ 4.0 │ 4 │
│ 5 │ false │ 5.0 │ 5 │
│ 6 │  true │ 6.0 │ 6 │
└───┴───────┴─────┴───┘
"""

    # Column table
    # --------------------------------------------------------------------------

    Tables.istable(::Type{<:MyColumnTable}) = true
    names(m::MyColumnTable) = getfield(m, :names)
    mat(m::MyColumnTable) = getfield(m, :matrix)
    lookup(m::MyColumnTable) = getfield(m, :lookup)

    Tables.columnaccess(::Type{<:MyColumnTable}) = true
    Tables.columns(m::MyColumnTable) = m
    Tables.getcolumn(m::MyColumnTable, ::Type{T}, col::Int, nm::Symbol) where {T} = mat(m)[:, col]
    Tables.getcolumn(m::MyColumnTable, nm::Symbol) = mat(m)[:, lookup(m)[nm]]
    Tables.getcolumn(m::MyColumnTable, i::Int) = mat(m)[:, i]
    Tables.columnnames(m::MyColumnTable) = names(m)

    table = MyColumnTable([:a,:b,:c,:d],
                          Dict(:a => 1, :b => 2, :c => 3, :d => 4),
                          data)

    result = pretty_table(String, table)

    @test Tables.schema(table) == nothing
    @test result == expected

    # Row table
    # --------------------------------------------------------------------------

    # First, we need to create a object that complies with Tables.jl and that
    # does not have a schema. This is based on Tables.jl documentation.

    Tables.istable(::Type{<:MyRowTable}) = true
    names(m::MyRowTable) = getfield(m, :names)
    mat(m::MyRowTable) = getfield(m, :matrix)
    lookup(m::MyRowTable) = getfield(m, :lookup)

    Tables.rowaccess(::Type{<:MyRowTable}) = true
    Tables.rows(m::MyRowTable) = m
    Base.eltype(m::MyRowTable{T}) where {T} = MyMatrixRow{T}
    Base.length(m::MyRowTable) = size(mat(m), 1)
    Base.iterate(m::MyRowTable, st=1) = st > length(m) ? nothing : (MyMatrixRow(st, m), st + 1)

    Tables.getcolumn(m::MyMatrixRow, ::Type, col::Int, nm::Symbol) =
        getfield(getfield(m, :source), :matrix)[getfield(m, :row), col]
    Tables.getcolumn(m::MyMatrixRow, i::Int) =
        getfield(getfield(m, :source), :matrix)[getfield(m, :row), i]
    Tables.getcolumn(m::MyMatrixRow, nm::Symbol) =
        getfield(getfield(m, :source), :matrix)[getfield(m, :row), getfield(getfield(m, :source), :lookup)[nm]]
    Tables.columnnames(m::MyMatrixRow) = names(getfield(m, :source))

    table = MyRowTable([:a,:b,:c,:d], Dict(:a => 1, :b => 2, :c => 3, :d => 4),
                       data)

    result = pretty_table(String, table)

    @test Tables.schema(table) == nothing
    @test result == expected
end

# Issue #4
# ==============================================================================

@testset "Issue #4 - Header with escape sequences" begin

    expected = """
┌───┬───────┬─────┬───┐
│ 1 │   2\\n │   3 │ 4 │
├───┼───────┼─────┼───┤
│ 1 │ false │ 1.0 │ 1 │
│ 2 │  true │ 2.0 │ 2 │
│ 3 │ false │ 3.0 │ 3 │
│ 4 │  true │ 4.0 │ 4 │
│ 5 │ false │ 5.0 │ 5 │
│ 6 │  true │ 6.0 │ 6 │
└───┴───────┴─────┴───┘
"""

    result = pretty_table(String, data, ["1" "2\n" "3" "4"])
    @test result == expected
end

# Issue #9
# ==============================================================================

@testset "Issue #9 - Printing table with row number column and sub-headers" begin
    expected = """
┌─────┬───┬───────┬─────┬────┐
│ Row │ 1 │     2 │   3 │  4 │
│     │ 5 │     6 │   7 │  8 │
│     │ 9 │    10 │  11 │ 12 │
├─────┼───┼───────┼─────┼────┤
│   1 │ 1 │ false │ 1.0 │  1 │
│   2 │ 2 │  true │ 2.0 │  2 │
│   3 │ 3 │ false │ 3.0 │  3 │
│   4 │ 4 │  true │ 4.0 │  4 │
│   5 │ 5 │ false │ 5.0 │  5 │
│   6 │ 6 │  true │ 6.0 │  6 │
└─────┴───┴───────┴─────┴────┘
"""
    result = pretty_table(String, data, [1  2  3  4;
                                         5  6  7  8;
                                         9 10 11 12];
                          show_row_number = true)
    @test result == expected
end

# Issue #10
# ==============================================================================

@testset "Issue #10 - Size of the row number column without header" begin
    v = 0:1:6

    expected = """
┌───┬───┐
│ 1 │ 0 │
│ 2 │ 1 │
│ 3 │ 2 │
│ 4 │ 3 │
│ 5 │ 4 │
│ 6 │ 5 │
│ 7 │ 6 │
└───┴───┘
"""

    result = pretty_table(String, v; noheader = true, show_row_number = true)
    @test result == expected
end

# Issue #16
# ==============================================================================

@testset "Issue #16 - Printing DataFrames that contains strings" begin
    df = DataFrame(:a => Int64[1, 2], :b => ["A", "B"]);

    expected = """
┌───────┬────────┐
│     a │      b │
│ Int64 │ String │
├───────┼────────┤
│     1 │      A │
│     2 │      B │
└───────┴────────┘
"""

    result = pretty_table(String, df)
    @test result == expected
end

# Issue #19
# ==============================================================================

@testset "Issue #19 - ft_printf with cells that are not numbers" begin
    matrix = [1 1; 2 2; 3 3; "teste" "teste"; 4 4; 5 5; true true; :s :s]

    expected = """
┌────────────┬────────┐
│     Col. 1 │ Col. 2 │
├────────────┼────────┤
│       1.00 │      1 │
│       2.00 │      2 │
│       3.00 │      3 │
│      teste │  teste │
│       4.00 │      4 │
│       5.00 │      5 │
│       1.00 │   true │
│          s │      s │
└────────────┴────────┘
"""

    result = pretty_table(String, matrix; formatters = ft_printf("%10.2f",[1]))
    @test result == expected

end

# Issue #22
# ==============================================================================

@testset "Issue #22 - Strings with quotes" begin
    # Without linebreaks.
    matrix = [1 "teste\"teste"
              2 "teste\"\"teste"]

    expected = """
┌────────┬──────────────┐
│ Col. 1 │       Col. 2 │
├────────┼──────────────┤
│      1 │  teste"teste │
│      2 │ teste""teste │
└────────┴──────────────┘
"""

    result = pretty_table(String, matrix)
    @test result == expected

    # With linebreaks.
    matrix = [ 1 """
    function str(str = "one string")
        return str
    end"""
    2 """
    function str(str = "")
        if isempty(str)
            return "one string"
        else
            return str
        end
    end"""]

    expected = """
┌────────┬──────────────────────────────────┐
│ Col. 1 │ Col. 2                           │
├────────┼──────────────────────────────────┤
│ 1      │ function str(str = "one string") │
│        │     return str                   │
│        │ end                              │
│ 2      │ function str(str = "")           │
│        │     if isempty(str)              │
│        │         return "one string"      │
│        │     else                         │
│        │         return str               │
│        │     end                          │
│        │ end                              │
└────────┴──────────────────────────────────┘
"""

    result = pretty_table(String, matrix; alignment = :l, linebreaks = true)

    @test result == expected
end

# Issue #24
# ==============================================================================

@testset "Issue #24 - Tables compatibility" begin
    # Named tuple of vectors (satisfies Tables interface).
    ctable = (x = Int64[1, 2, 3, 4], y = ["a", "b", "c", "d"])

    cresult = sprint(pretty_table, ctable)

    expected = """
┌───────┬────────┐
│     x │      y │
│ Int64 │ String │
├───────┼────────┤
│     1 │      a │
│     2 │      b │
│     3 │      c │
│     4 │      d │
└───────┴────────┘
"""

  @test cresult == expected
end

# Issue #28
# ==============================================================================

@testset "Issue #28 - Tables.jl API must have priority when printing" begin
    # A DataFrame is compliant with Tables.jl API.
    df = DataFrame(x = Int64(1):Int64(3),
                   y = 'a':'c',
                   z = ["String 1";"String 2";"String 3"]);

    # Thus, the following 3 calls must provide the same results.
    result_1 = pretty_table(String, df)
    result_2 = pretty_table(String, Tables.rowtable(df))
    result_3 = pretty_table(String, Tables.columns(df))

    expected = """
┌───────┬──────┬──────────┐
│     x │    y │        z │
│ Int64 │ Char │   String │
├───────┼──────┼──────────┤
│     1 │    a │ String 1 │
│     2 │    b │ String 2 │
│     3 │    c │ String 3 │
└───────┴──────┴──────────┘
"""

    @test result_1 == result_2 == result_3 == expected
end


text:
# This is part of Calendars.jl. See the copyright note there.
# ========================= ISO dates =======================

# Day number of the start of the ISO calendar.
# ID-0001-01-01 = CE-0001-01-01 ~ DN#1 = EpochIso.
# ID-1111-45-04 = CE-1111-11-11 ~ DN#0405733
# ID-9999-52-05 = CE-9999-12-31 ~ DN#3652059
const EpochIso = 1
const ValidYearsIso = (0, 9999) # by convention

# Day of week in ISO is represented by an integer from 1 through 7,
# beginning with Monday and ending with Sunday, which is
# different from our enumeration, so we have to adjust.
function DayOfWeekIso(dn::DPart)
    day = rem(dn, weeklen)
    return day == 0 ? 7 : day
end

# True if year is an Iso leap year.
function isLeapYearIso(year::DPart)
    f(y) = rem(y + div(y, 4) - div(y, 100) + div(y, 400), 7)
    f(year) == 4 || f(year - 1) == 3
end

# Return the last week of the year for the Iso calendar.
function LastWeekOfYearIso(year::DPart)
    isLeapYearIso(year) ? 53 : 52
end

# Is the date a valid Iso date?
function isValidDateIso(cd::CDate, warn=true)
    cal, year, week, day = cd
    val = ( cal == ID
        && (ValidYearsIso[1] <= year && year <= ValidYearsIso[2])
        && (1 <= week && week <= LastWeekOfYearIso(year))
        && (1 <= day && day <= 7)
    )
    ! val && warn && @warn(Warning(cd))
    return val
end

# Represents the day number a valid Iso date?
function isValidDateIso(dn::DPart, warn=true)
    # val = EpochGregorian - 2 <= dn
    val = -1 <= dn
    ! val && warn && @warn(Warning(dn))
    return val
end

# Return the days this year so far.
# Does not depend on 'year' but keep it in the signature.
function DayOfYearIso(year::DPart, week::DPart, day::DPart)
    day += 7 * (week - 1)  # days in prior weeks this year
    return day - 1
end

XdayOnOrBefore(dn, x) = dn - rem(dn - x, weeklen)

# Return the number of days in prior years.
function DaysInPriorYears(year::DPart)
    # dn = DayNumberGregorian(year, 1, 4)
    # Let's make it self-contained!
    dn = ( 4           # days this year
    + 365 * year       # days in previous years ignoring leap days
    + div(year, 4)     # Julian leap days before this year...
    - div(year, 100)   # ...minus prior century years...
    + div(year, 400)   # ...plus prior years divisible by 400
    )
    return XdayOnOrBefore(dn, 1)
end

# Returns the day number from a valid ISO date.
function DayNumberIso(year::DPart, week::DPart, day::DPart)
    (
    DaysInPriorYears(year - 1) # days in prior years
    + 7 * (week - 1)           # days in prior weeks this year
    + day - 1                  # prior days this week
    )
end

# Computes the day number from a valid ISO date.
DayNumberIso(d::CDate) = DayNumberIso(d[2], d[3], d[4])

# Computes the ISO date from a day number.
function DateIso(dn::DPart)
    ! isValidDateIso(dn) && return InvalidDate

    # Patch in the two allowed exceptions.
    dn == -1 && return (ID, 0, 52, 6)
    dn ==  0 && return (ID, 0, 52, 7)

    # year = DateGregorian(dn - 3)[2]
    # Let's make it self-contained!
    year = div(400 * (dn - 4), 146097) + 1

    if dn >= YearStartIso(year + 1)
        year += 1
    end
    week = 1 + div(dn - YearStartIso(year), weeklen)
    day = DayOfWeekIso(dn)
    return (ID, year, week, day)::CDate
end

# Return the day number of the first day in the given Iso year.
function YearStartIso(year::DPart)
    return DayNumberIso(year, 1, 1)
end

# Return the day number of the last day in the given Iso year.
function YearEndIso(year::DPart)
    return DayNumberIso(year + 1, 1, 1) - 1
end

# Return the number of weeks in the given Iso year.
function WeeksInYearIso(year::DPart)
    return LastWeekOfYearIso(year)
end

# Return the number of days in the given Iso year.
function DaysInYearIso(year::DPart)
    return YearStartIso(year + 1) - YearStartIso(year)
end

ext: jl


text:
using PyTest
using Base.Test

# FIXME: there needs to be this top-level @testset for this to make sense
@testset begin
  @pytest function one() @test 1 == 1 end
  @pytest function two() @test 1 == 1 end
  @pytest function three() @test 1 == 1 end
end


text:
function spharm( l, m, Gvec, cost, sint, phi )

  Ng = size(Gvec)[2]

  ylm = zeros( Float64, Ng )

  if l == 0
    for ig = 1:Ng
      ylm[ig] = 0.5*sqrt(1.0/pi)
    end
    return ylm

  elseif l == 1
    # py
    if m == -1
      for ig = 1:Ng
        ylm[ig] = 0.5*sqrt(3/pi)*sint[ig]*sin(phi[ig])
      end
      return ylm
    # pz
    elseif m == 0
      for ig = 1:Ng
        ylm[ig] = 0.5*sqrt(3/pi)*cost[ig]
      end
      return ylm
    # px
    elseif m == 1
      for ig = 1:Ng
        ylm[ig] = 0.5*sqrt(3/pi)*sint[ig]*cos(phi[ig])
      end
      return ylm
    end

  elseif l == 2
    # dxy
    if m == -2
      for ig = 1:Ng
        ylm[ig] = sqrt(15/16/pi)*sint[ig]^2*sin(2*phi[ig])
      end
      return ylm
    # dyz
    elseif m == -1
      for ig = 1:Ng
        ylm[ig] = sqrt(15/(4*pi))*cost[ig]*sint[ig]*sin(phi[ig])
      end
      return ylm
    # dz2
    elseif m == 0
      for ig = 1:Ng
        ylm[ig] = 0.25*sqrt(5/pi)*( 3*cost[ig]^2 - 1 )
      end
      return ylm
    # dxz
    elseif m == 1
      for ig = 1:Ng
        ylm[ig] = sqrt(15/4/pi)*cost[ig]*sint[ig]*cos(phi[ig])
      end
      return ylm
    # dx2-y2
    elseif m == 2
      for ig = 1:Ng
        ylm[ig] = 0.5*sqrt(15/4/pi)*sint[ig]^2*cos(2*phi[ig])
      end
      return ylm

    end

  elseif l == 3

    if m == -3
      for ig = 1:Ng
        ylm[ig] = 0.25*sqrt(35/2/pi)*sint[ig]^3*sin(3*phi[ig])
      end
      return ylm

    elseif m == -2
      for ig = 1:Ng
        ylm[ig] = 0.25*sqrt(105/pi)*sint[ig]^2*cost[ig]*sin(2*phi[ig])
      end
      return ylm

    elseif m == -1
      for ig = 1:Ng
        ylm[ig] = 0.25*sqrt(21/2/pi)*sint[ig]*( 5*cost[ig]^2 - 1 )*sin(phi[ig])
      end
      return ylm

    elseif m == 0
      for ig = 1:Ng
        ylm[ig] = 0.25*sqrt(7/pi)*( 5*cost[ig]^3 - 3*cost[ig] )
      end
      return ylm

    elseif m == 1
      for ig = 1:Ng
        ylm[ig] = 0.25*sqrt(21/2/pi)*sint[ig]*( 5*cost[ig]^2 - 1 )*cos(phi[ig])
      end
      return ylm

    elseif m == 2
      for ig = 1:Ng
        ylm[ig] = 0.25*sqrt(105/pi)*sint[ig]^2*cost[ig]*cos(2*phi[ig])
      end
      return ylm

    elseif m == 3
      for ig = 1:Ng
        ylm[ig] = 0.25*sqrt(35/2/pi)*sint[ig]^3*cos(3*phi[ig])
      end
      return ylm

    end

  end

end


text:
using PlotlyJS, Random, DSP, Dierckx, FileIO, JLD2

# This script makes the trace data to compare a step in the tine shifts

#ricker wavelet function
"""
ricker(omega)
returns Ricker wavelet

input:

    peak frequency   = 20 Hz
    sampling time    = 0.001 seconds
    number of points = 100;

output:
    s: ricker wavelet with length = number of points
    t: time
"""
function ricker(;f::Float64=20.0, n::Int64=100, dt::Float64=0.001, nargout::Bool=true)
    # Create the wavelet and shift in time if needed
    T = dt*(n-1);
    t = collect(0:dt:T);
    t0 = 1/f;
    tau = t.-t0;

    s = (1.0 .- tau.*tau.*f.^2*π.^2).*exp.(-tau.^2*π.^2*f.^2);

    if nargout
        trace = scatter(;x=t, y=s, mode="lines")
        layout = Layout(
            xaxis=attr(title="Time"),
            yaxis=attr(title="u"),
        )
        plot(trace, layout)
    end

    return (s, t)
end

# make Ricker wavelet

dt     = 0.004; # s
f      = 20.0; # Hz
w,tw = ricker(f=f, n=25, dt=dt, nargout=true);

## make random reflectivity sequence and convolve with Ricker

npts = 500; # length of time series

rng = MersenneTwister(20190602)
f    = randn(rng, Float64, npts);
u0   = conv(f,w)[1:npts]; # make waveform time series

## make time varying shifts as sine wave

amp = 0.2 * dt;
p   = LinRange(0, 2*pi, npts);
dp  = p[2];
st   = amp .* sin.(p) ./ dt;

tvec  = collect(( 0:npts - 1 ) .* dt);
tvec2 = tvec .+ st;

spl = Spline1D(tvec, u0)
u1 = spl(tvec2);

st = st;

@save "./sineShiftData.jld2" dt u0 u1 st

if nargout

    #time shift curve

    function lineplot1()
        tr = scatter(;x=tvec, y=st, mode="lines", name="Timeshift")
        layout = Layout(
            xaxis=attr(title="Time"),
            yaxis=attr(title="Timeshift"),
        )
        plot(tr, layout)
    end

    function lineplot2()
        tr1 = scatter(;x=tvec, y=u0, mode="lines+markers", name="u0")
        tr2 = scatter(;x=tvec, y=u1, mode="lines+markers", name="u1")
        layout = Layout(
            xaxis=attr(title="Time"),
            yaxis=attr(title="Signal"),
        )
        plot([tr1, tr2], layout)
    end

    p = [lineplot1(), lineplot2()]

end


text:
"""
    savefield(mesh, ξ, filename::String; fieldname = "Magnetization")

Save field `ξ` to a VTK file. It may then be visualized in Paraview.
"""
function savefield(mesh, ξ, filename::String; fieldname = "Magnetization")
    ξ_cmpts = split_field(mesh, ξ)
    isdir(dirname(filename)) || mkpath(dirname(filename))
    vtmfile = vtk_multiblock(filename)
    for icmpt = 1:length(mesh.points)
        ξᵢ = ξ_cmpts[icmpt]
        points = mesh.points[icmpt]
        elements = mesh.elements[icmpt]
        cells =
            [MeshCell(VTKCellTypes.VTK_TETRA, elements[:, i]) for i = 1:size(elements, 2)]
        vtkfile = vtk_grid(vtmfile, points, cells)
        vtkfile[fieldname*" (real part)"] = real(ξᵢ)
        vtkfile[fieldname*" (imaginary part)"] = imag(ξᵢ)
    end
    vtk_save(vtmfile)
end


text:
# This file was generated, do not modify it. # hide
using Plots

@userplot CirclePlot
@recipe function f(cp::CirclePlot)
    x, y, i = cp.args
    n = length(x)
    inds = circshift(1:n, 1 - i)
    linewidth --> range(0, 10, length = n)
    seriesalpha --> range(0, 1, length = n)
    aspect_ratio --> 1
    label --> false
    x[inds], y[inds]
end

n = 400
t = range(0, 2π, length = n)
x = 16sin.(t).^3
y = 13cos.(t) .- 5cos.(2t) .- 2cos.(3t) .- cos.(4t)
anim = @animate for i ∈ 1:n
    circleplot(x, y, i, line_z = 1:n, cbar = false, c = :reds, framestyle = :none)
end when i > 40 && mod1(i, 10) == 5

gif(anim, joinpath(@OUTPUT, "granim.gif")) #hide


text:
using NumericalMethodsforEngineers

ProjDir = dirname(@__FILE__)
cd(ProjDir) do

  function f(x::Float64, y::Vector{Float64})
    [y[2], -2y[2]/y[1]]
  end

  res = shootingmethod(f, [0.0, 1.0, 1.0, 2.0], [1.0, 3.0], 10, 0.00001, 40)
  @show round.(res, 4)

end


text:
using PQBaseCamp
using Test

# -- Default test ------------------------------------------------------ #
function default_pqbasecamp_test()
    return true
end
# ------------------------------------------------------------------------------- #

@testset "default_test_set" begin
    @test default_pqbasecamp_test() == true
end


text:
#Space class
abstract type SimulationSpace end

#FlatBoundary classes
abstract type FlatBoundary end
abstract type NonPeriodicFlat<:FlatBoundary end

max_stars_repo_name: gatocor/embryogenesisJulia

max_issues_repo_name: gatocor/embryogenesisJulia

max_forks_repo_name: gatocor/embryogenesisJulia


text:
#
#
# JacobiFields
#
#
export βDgx, βDexpx, βDexpξ, βDlogx, βDlogy

@doc doc"""
    βDgx(κ,t,d)
weights for the [`jacobiField`](@ref) corresponding to the differential of the geodesic
with respect to its start point $D_x g(t;x,y)[\eta]$.
They are

```math
\beta(\kappa) = \begin{cases}
\frac{\sinh(d(1-t)\sqrt{-\kappa})}{\sinh(d\sqrt{-\kappa})}
&\text{ if }\kappa < 0,\\
1-t & \text{ if } \kappa = 0,\\
\frac{\sin((1-t)d\sqrt{\kappa})}{\sinh(d\sqrt{\kappa})}
&\text{ if }\kappa > 0.
\end{cases}
```

Due to a symmetry agrument, these are also used to compute $D_y g(t; x,y)[\eta]$

# See also
 [`DyGeo`](@ref), [`DxGeo`](@ref), [`jacobiField`](@ref)
"""
function βDgx(κ::Number,t::Number,d::Number)
    if (d==0) || (κ==0)
        return (1-t)
    else
        if κ < 0
            return sinh(sqrt(-κ)*(1-t)*d)/sinh(sqrt(-κ)*d)
        elseif κ > 0
            return sin(sqrt(κ)*(1-t)*d)/sin(sqrt(κ)*d)
        end
    end
end
@doc doc"""
    βDexpx(κ,t,d)

weights for the [`jacobiField`](@ref) corresponding to the differential of the geodesic
with respect to its start point $D_x \exp_x(\xi)[\eta]$. They are

```math
\beta(\kappa) = \begin{cases}
\cosh(\sqrt{-\kappa})&\text{ if }\kappa < 0,\\
1 & \text{ if } \kappa = 0,\\
\cos(\sqrt{\kappa}) &\text{ if }\kappa > 0.
\end{cases}
```

# See also
 [`DxExp`](@ref), [`jacobiField`](@ref)
"""
function βDexpx(κ::Number,t::Number,d::Number)
    if κ < 0
        return cosh(sqrt(-κ)*d)
    elseif κ > 0
        return cos(sqrt(κ)*d)
    else
        return 1.0;
    end
end
@doc doc"""
    βDexpξ(κ,t,d)

weights for the [`jacobiField`](@ref) corresponding to the differential of the geodesic
with respect to its start point $D_\xi \exp_x(\xi)[\eta]$. They are

```math
$\beta(\kappa) = \begin{cases}
\frac{\sinh(d\sqrt{-\kappa})}{d\sqrt{-\kappa}}&\text{ if }\kappa < 0,\\
1 & \text{ if } \kappa = 0,\\
\frac{\sin(d\sqrt{\kappa})}{\sqrt{d\kappa}}&\text{ if }\kappa > 0.
\end{cases}
```

# See also
 [`DξExp`](@ref), [`jacobiField`](@ref)
"""
function βDexpξ(κ::Number,t::Number,d::Number)
    if (d==0) || (κ==0)
        return 1.0
    else
        if κ < 0
            return sinh(sqrt(-κ)*d)/( d*sqrt((-κ)) )
        elseif κ > 0
            return sin( sqrt(κ)*d )/( d*sqrt(κ) )
        end
    end
end
@doc doc"""
    βDlogx(κ,t,d)

weights for the[`jacobiField`](@ref) corresponding to the differential of the geodesic
with respect to its start point $D_x \log_xy[\eta]$. They are

```math
\beta(\kappa) = \begin{cases}
-\sqrt{-\kappa}d\frac{\cosh(d\sqrt{-\kappa})}{\sinh(d\sqrt{-\kappa})}&\text{ if }\kappa < 0,\\
-1 & \text{ if } \kappa = 0,\\
-\sqrt{\kappa}d\frac{\cos(d\sqrt{\kappa})}{\sin(d\sqrt{\kappa})}&\text{ if }\kappa > 0.
\end{cases}
```

# See also
[`DxLog`](@ref), [`DyLog`](@ref), [`jacobiField`](@ref)
"""
function βDlogx(κ::Number,t::Number,d::Number)
    if (d==0) || (κ==0)
        return -1.0
    else
        if κ < 0
            return - sqrt(-κ)*d*cosh(sqrt(-κ)*d)/sinh(sqrt(-κ)*d)
        else #if κ > 0
            return - sqrt(κ)*d*cos(sqrt(κ)*d)/sin(sqrt(κ)*d)
        end
    end
end
@doc doc"""
    βDlogy(κ,t,d)

weights for the JacobiField corresponding to the differential of the logarithmic
map with respect to its argument $D_y \log_xy[\eta]$.
They are
```math
\beta(\kappa) = \begin{cases}
\frac{ d\sqrt{-\kappa} }{\sinh(d\sqrt{-\kappa})}&\text{ if }\kappa < 0,\\
1 & \text{ if } \kappa = 0,\\
\frac{ d\sqrt{\kappa} }{\sin(d\sqrt{\kappa})}&\text{ if }\kappa > 0.
\end{cases}
```

# See also
 [`DyLog`](@ref), [`jacobiField`](@ref)
"""
function βDlogy(κ::Number,t::Number,d::Number)
    if (d==0) || (κ==0)
        return 1.0
    else
        if κ < 0
            return sqrt(-κ)*d/sinh(sqrt(-κ)*d)
        else #if κ > 0
            return sqrt(κ)*d/sin(sqrt(κ)*d)
        end
    end
end


text:
using Test
using StaticArrays
using VariableTemplates

@testset "Simple models" begin
    abstract type Model end
    struct SGSModel <: Model end
    struct MoistureModel <: Model
        sgs
    end
    struct AtmosModel <: Model
        moisture
    end

    function vars_state(m::SGSModel, FT)
        @vars begin
            x::FT
        end
    end
    function vars_state(m::MoistureModel, FT)
        @vars begin
            x::FT
            sgs::vars_state(m.sgs, FT)
        end
    end
    function vars_state(m::AtmosModel, FT)
        @vars begin
            ρ::FT
            ρu::SVector{3, FT}
            ρe::FT
            moisture::vars_state(m.moisture, FT)
        end
    end

    FT = Float32;

    # AtmosModel
    sgs = SGSModel()
    st = vars_state(sgs, FT)
    nt = concretize(st)
    @test nt.x.type == Val(FT)
    @test nt.x.gid == Val(1)

    m = MoistureModel(sgs)
    st = vars_state(m, FT)
    nt = concretize(st)
    @test nt.x.type == Val(FT)
    @test nt.x.gid == Val(1)
    @test nt.sgs.x.type == Val(FT)
    @test nt.sgs.x.gid == Val(2)

    atmos = AtmosModel(m)
    st = vars_state(atmos, FT)
    nt = concretize(st)
    @test nt.ρ.type == Val(FT)
    @test nt.ρ.gid == Val(1)
    @test nt.ρu.type == Val(SVector{3, FT})
    @test nt.ρu.gid == Val(2)
    @test nt.ρe.type == Val(FT)
    @test nt.ρe.gid == Val(5)
    @test nt.moisture.x.type == Val(FT)
    @test nt.moisture.x.gid == Val(6)
    @test nt.moisture.sgs.x.type == Val(FT)
    @test nt.moisture.sgs.x.gid == Val(7)

    vs = varsize(nt)
    a_global = collect(1:vs)
    v = Vars{nt}(a_global)
    @test VariableTemplates.get_tup_chain(v) == ()
    ρ = v.ρ
    @test ρ == 1
    ρu = v.ρu
    @test ρu == [2,3,4]
    ρe = v.ρe
    @test ρe == 5
    moisture = v.moisture
    @test VariableTemplates.get_tup_chain(moisture) == (:moisture,)
    sgs = moisture.sgs
    @test VariableTemplates.get_tup_chain(sgs) == (:moisture, :sgs)
    x = sgs.x
    @test x == 7
end


text:
using JuMP
include("../src/JOH.jl")

function one_hot_encode(index, group, n_groups)
    enc = zeros(n_groups)
    enc[index] = 1
    map(elem->vcat(elem.resources, enc), group)
end

function jump_model(problem::MMKP_Data, num_threads)::Model
    model = Model()
	JOH.Matheur.set_threads!(model, num_threads)

    #get matrix of resource requirements per item
    n_groups = length(problem.groups)
    groups = map(t -> one_hot_encode(t[1], t[2], n_groups), enumerate(problem.groups))
    flat_groups = vcat(groups...)
    resource_mat = hcat(flat_groups...)

    #add the one-hot dimensions to the resource bounds
    res_lims = vcat(problem.resource_bounds, ones(n_groups))

    #extract the profit
    profit = vcat(map(g->[i.profit for i in g], problem.groups)...)

    #make the model
    @variable(model, x[1:length(profit)], Bin)
    @objective(model, Max, sum(profit .* x))
    for (i, row) in enumerate(eachrow(resource_mat))
        @constraint(model, sum(row .* x) <= res_lims[i])
    end

    model
end

max_stars_repo_name: Dibillilia/JuliaOptHeuristics

max_issues_repo_name: Dibillilia/JuliaOptHeuristics

max_forks_repo_name: Dibillilia/JuliaOptHeuristics


text:
export poynting, powerₗ

# The function Re{⋅} is linear for real coefficients.  In other words, for complex numbers
# z = x + iy and w = u + iv,
#
# Re{p z + q w} = Re{p(x+iy) + q(u+iv)} = p x + q u = p Re{z} + q Re{w}.
#
# Therefore, when averaging the E- and H-fields for the Poynting vector calculation, the
# weight factors ∆l shouldn't contain the PML scale factors (i.e., we should not use s∆l).
# As long as we use real ∆l, we can average the Poynting vector before the last step, e.g.,
# Sz = ½ Re{Ex Hy＊ - Ey Hx＊} = ½ Re{Ex Hy＊} - ½ Re{Ey Hx＊}.
#
# Also note that the time-averaged Poynting vector is meaningful only for real frequency ω.
# This is usually not a problem because
# - in source-driven problems we use real frequency;
# - in waveguide mode problems we use ω-to-β formulation, where we use real ω and calculate
# complex β.
# However, note that in SALT we deal with complex ω below the lasing threshold.  Thankfully
# we don't need to calculate the Poynting vector for such nonlasing modes.
#
# I don't care about eliminating array allocations here, because the Poynting vector
# calculation is much cheaper than solution calculation.  If I really need to eliminate
# allocations, consider using for loops; otherwise such elimination will be difficult
# because of field averaging.
#
# For simplicity, always calculate the Poynting vectors as quantities entering the E-field
# voxels.
#
# On the Yee grid, the different Cartesian components of different vector fields are defined
# at unique locations.  Positioning these Cartesian components of vector fields at their
# correct locations (with the correct half-grid-point offsets) can be confusing, because
# there are quite a few different Yee grid types.  The 3D Yee grid for the 3D Maxwell
# equations is the most standard configuration, so the positions of the Cartesian components
# of the E- and H-fields in there are relatively well-known.  But in the grid configurations
# for other types of equations, such as the 2D TE and TM Maxwell equations and even the
# waveguide mode equation, the correct positions of the Cartesian components of the E- and H-
# fields can be confusing.  The correct positions of the Cartesian components of the
# Poyinting vector are even more confusing, because their calculation involves cross
# products and averaging.  So, the users must make sure to know where their vector fields
# are defined.  In the future, I may need to provide some wrapper functions that help the
# users on this regard.
function poynting end

# Used in poynting().
function interp_field!(G::AbsArrComplexF{K},  # where averaged fields are stored
                       F::AbsArrComplexF{K},  # field to average
                       ft::FieldType,  # type of field to average
                       nw::Int,  # direction of averaging
                       ∆l::Tuple2{NTuple{K,VecFloat}},
                       ∆l⁻¹::Tuple2{NTuple{K,VecFloat}},
                       boundft::SVec{K,FieldType},
                       isbloch::SBool{K};
                       kwargs...  # keyword arguments for apply_m̂!()
                       ) where {K}
    nft = Int(ft); nft′ = alter(nft)
    isfwd = boundft[nw]==ft
    apply_m̂!(G, F, nw, isfwd, ∆l[nft][nw], ∆l⁻¹[nft′][nw], isbloch[nw]; kwargs...)  # intepolate Hz at Ey-locations

    return nothing
end

# Calculate the power along the longitudinal direction.
function powerₗ(E::NTuple{Kₑ,AbsArrNumber{K}},
                H::NTuple{Kₘ,AbsArrNumber{K}},
                mdl::Model{K,Kₑ,Kₘ}
                ) where {K,Kₑ,Kₘ}
    S = poynting(E, H, mdl)
    Sz = S[end]

    gtₑ = ft2gt.(HH, mdl.boundft)
    lvxl = t_ind(mdl.grid.ghosted.l, gtₑ)  # locations of x-normal edges of E-field pixels surrounding Sz

    vc = VoxelwiseConstant(Sz, lvxl)
    Pz = integral(vc)

    return Pz
end

function LinearAlgebra.normalize!(E::NTuple{Kₑ,AbsArrNumber{K}},
                                  H::NTuple{Kₘ,AbsArrNumber{K}},
                                  mdl::Model{K,Kₑ,Kₘ}
                                  ) where {K,Kₑ,Kₘ}
    Pz = powerₗ(E, H, mdl)
    sqrtPz = √Pz

    for k = 1:Kₑ
        E[k] ./= sqrtPz
    end

    for k = 1:Kₘ
        H[k] ./= sqrtPz
    end

    return nothing
end