"""
    X = sylvc(A,B,C)

Solve the continuous Sylvester matrix equation

                AX + XB = C

using the Bartels-Stewart Schur form based approach. `A` and `B` are
square matrices, and `A` and `-B` must not have common eigenvalues.

The following particular cases are also adressed:

    X = sylvc(α*I,B,C)  or  X = sylvc(α,B,C)

Solve the matrix equation `X(αI+B)  = C`.

    X = sylvc(A,β*I,C)  or  X = sylvc(A,β,C)

Solve the matrix equation `(A+βI)X = C`.

    X = sylvc(α*I,β*I,C)  or  sylvc(α,β,C)

Solve the matrix equation `(α+β)X = C`.

    x = sylvc(α,β,γ)

Solve the equation `(α+β)x = γ`.

# Example
```jldoctest
julia> A = [3. 4.; 5. 6.]
2×2 Array{Float64,2}:
 3.0  4.0
 5.0  6.0

julia> B = [1. 1.; 1. 2.]
2×2 Array{Float64,2}:
 1.0  1.0
 1.0  2.0

julia> C = [-1. -2.; 2. -1.]
2×2 Array{Float64,2}:
 -1.0  -2.0
  2.0  -1.0

julia> X = sylvc(A, B, C)
2×2 Array{Float64,2}:
 -4.46667   1.93333
  3.73333  -1.8

julia> A*X + X*B - C
2×2 Array{Float64,2}:
  2.66454e-15  1.77636e-15
 -3.77476e-15  4.44089e-16
```
"""
function sylvc(A::AbstractMatrix,B::AbstractMatrix,C::AbstractMatrix)
   """
   Reference:
   R. H. Bartels and G. W. Stewart. Algorithm 432: Solution of the matrix equation AX+XB=C.
   Comm. ACM, 15:820–826, 1972.
   """

   m, n = size(C);
   [m; n] == LinearAlgebra.checksquare(A,B) || throw(DimensionMismatch("A, B and C have incompatible dimensions"))

   T2 = promote_type(eltype(A), eltype(B), eltype(C))
   T2 <: BlasFloat || (T2 = promote_type(Float64,T2))
   eltype(A) == T2 || (A = convert(Matrix{T2},A))
   eltype(B) == T2 || (B = convert(Matrix{T2},B))
   eltype(C) == T2 || (C = convert(Matrix{T2},C))

   adjA = isa(A,Adjoint)
   adjB = isa(B,Adjoint)
   if adjA
      RA, QA = schur(A.parent)
   else
      RA, QA = schur(A)
   end
   if adjB
      RB, QB = schur(B.parent)
   else
      RB, QB = schur(B)
   end

   Y = QA' * C * QB

   sylvcs!(RA, RB, Y, adjA = adjA, adjB = adjB)

   mul!(Y, QA, Y*QB')

   return Y
end
# solve X(B+α) = C or (α+β)X = C
sylvc(A::Union{Real,Complex,UniformScaling},B::Union{AbstractMatrix,UniformScaling},C::AbstractMatrix) = C/(A*I+B)
# solve (A+β)X = C
sylvc(A::AbstractMatrix,B::Union{Real,Complex,UniformScaling},C::AbstractMatrix) = (A+B*I)\C
# solve (α+β)X = C
sylvc(A::Union{Real,Complex},B::Union{Real,Complex},C::AbstractMatrix) = A+B == 0 ? throw(SingularException(1)) : C/(A+B)
# solve (α+β)x = γ
sylvc(A::Union{Real,Complex}, B::Union{Real,Complex}, C::Union{Real,Complex}) = A+B == 0 ? throw(SingularException(1)) : C/(A+B)
"""
    X = sylvd(A,B,C)

Solve the discrete Sylvester matrix equation

                AXB + X = C

using an extension of the Bartels-Stewart Schur form based approach.
`A` and `B` are square matrices, and `A` and `-B` must not have
common reciprocal eigenvalues.

The following particular cases are also adressed:

    X = sylvd(α*I,B,C)  or  X = sylvd(α,B,C)

Solve the matrix equation `X(αB+I)  = C`.

    X = sylvd(A,β*I,C)   or  X = sylvd(A,β,C)

Solve the matrix equation `(βA+I)X = C`.

    X = sylvd(α*I,β*I,C)  or  X = sylvd(α,β,C)

Solve the matrix equation `(αβ+1)X = C`.

    x = sylvd(α,β,γ)

Solve the equation `(αβ+1)x = γ`.

# Example
```jldoctest
julia> A = [3. 4.; 5. 6.]
2×2 Array{Float64,2}:
 3.0  4.0
 5.0  6.0

julia> B = [1. 1.; 1. 2.]
2×2 Array{Float64,2}:
 1.0  1.0
 1.0  2.0

julia> C = [-1. -2.; 2. -1.]
2×2 Array{Float64,2}:
 -1.0  -2.0
  2.0  -1.0

julia> X = sylvd(A, B, C)
2×2 Array{Float64,2}:
 -2.46667  -2.73333
  2.4       1.86667

julia> A*X*B + X - C
2×2 Array{Float64,2}:
  8.88178e-16   8.88178e-16
 -3.9968e-15   -5.55112e-15
```
"""
function sylvd(A::AbstractMatrix,B::AbstractMatrix,C::AbstractMatrix)
   """
   Reference:
   R. H. Bartels and G. W. Stewart. Algorithm 432: Solution of the matrix equation AX+XB=C.
   Comm. ACM, 15:820–826, 1972.
   """

   m, n = size(C);
   [m; n] == LinearAlgebra.checksquare(A,B) || throw(DimensionMismatch("A, B and C have incompatible dimensions"))
   T2 = promote_type(eltype(A), eltype(B), eltype(C))
   T2 <: BlasFloat || (T2 = promote_type(Float64,T2))
   eltype(A) == T2 || (A = convert(Matrix{T2},A))
   eltype(B) == T2 || (B = convert(Matrix{T2},B))
   eltype(C) == T2 || (C = convert(Matrix{T2},C))

   adjA = isa(A,Adjoint)
   adjB = isa(B,Adjoint)
   if adjA
      RA, QA = schur(A.parent)
   else
      RA, QA = schur(A)
   end
   if adjB
      RB, QB = schur(B.parent)
   else
      RB, QB = schur(B)
   end

   Y = QA' * C * QB

   sylvds!(RA, RB, Y, adjA = adjA, adjB = adjB)

   mul!(Y, QA, Y*QB')

   return Y
end
# solve X(αB+I) = C or X(αβ+1) = C
sylvd(A::Union{Real,Complex,UniformScaling},B::Union{AbstractMatrix,UniformScaling},C::AbstractMatrix) = C/(A*B+I)
# solve (Aβ+I)X = C
sylvd(A::AbstractMatrix,B::Union{Real,Complex,UniformScaling},C::AbstractMatrix) = (A*B+I)\C
# solve (αβ+1)X = C
sylvd(A::Union{Real,Complex},B::Union{Real,Complex},C::AbstractMatrix) = A*B+1 == 0 ? throw(SingularException(1)) : C/(A*B+1)
# solve (αβ+1)x = γ
sylvd(A::Union{Real,Complex}, B::Union{Real,Complex}, C::Union{Real,Complex}) = A*B+1 == 0 ? throw(SingularException(1)) : C/(A*B+one(C))
"""
    X = gsylv(A,B,C,D,E)

Solve the generalized Sylvester matrix equation

              AXB + CXD = E

using a generalized Schur form based approach. `A`, `B`, `C` and `D` are
square matrices. The pencils `A-λC` and `D+λB` must be regular and
must not have common eigenvalues.

The following particular cases are also adressed:

    X = gsylv(A,B,E)

Solve the generalized Sylvester matrix equation `AXB  = E`.

    X = gsylv(A,B,γ*I,E)  or  X = gsylv(A,B,γ,E)

Solve the generalized Sylvester matrix equation `AXB +γX = E`.

    X = gsylv(A,B,γ*I,D,E)  or  X = gsylv(A,B,γ,D,E)

Solve the generalized Sylvester matrix equation `AXB +γXD = E`.

    X = gsylv(A,B,C,δ*I,E)  or  X = gsylv(A,B,C,δ,E)

Solve the generalized Sylvester matrix equation `AXB +CXδ = E`.

# Example
```jldoctest
julia> A = [3. 4.; 5. 6.]
2×2 Array{Float64,2}:
 3.0  4.0
 5.0  6.0

julia> B = [1. 1.; 1. 2.]
2×2 Array{Float64,2}:
 1.0  1.0
 1.0  2.0

julia> C = [-1. -2.; 2. -1.]
2×2 Array{Float64,2}:
 -1.0  -2.0
  2.0  -1.0

julia> D = [1. -2.; -2. -1.]
2×2 Array{Float64,2}:
  1.0  -2.0
 -2.0  -1.0

julia> E = [1. -1.; -2. 2.]
2×2 Array{Float64,2}:
  1.0  -1.0
 -2.0   2.0

julia> X = gsylv(A, B, C, D, E)
2×2 Array{Float64,2}:
 -0.52094   -0.0275792
 -0.168539   0.314607

julia> A*X*B + C*X*D - E
2×2 Array{Float64,2}:
 4.44089e-16  8.88178e-16
 6.66134e-16  0.0
```
"""
function gsylv(A::AbstractMatrix,B::AbstractMatrix,C::AbstractMatrix,D::AbstractMatrix,E::AbstractMatrix)

    m, n = size(E);
    [m; n; m; n] == LinearAlgebra.checksquare(A,B,C,D) ||
       throw(DimensionMismatch("A, B, C, D and E have incompatible dimensions"))
    T2 = promote_type(eltype(A), eltype(B), eltype(C), eltype(D), eltype(E))
    T2 <: BlasFloat || (T2 = promote_type(Float64,T2))
    eltype(A) == T2 || (A = convert(Matrix{T2},A))
    eltype(B) == T2 || (B = convert(Matrix{T2},B))
    eltype(C) == T2 || (C = convert(Matrix{T2},C))
    eltype(D) == T2 || (D = convert(Matrix{T2},D))
    eltype(E) == T2 || (E = convert(Matrix{T2},E))

    adjA = isa(A,Adjoint)
    adjB = isa(B,Adjoint)
    adjC = isa(C,Adjoint)
    adjD = isa(D,Adjoint)
    adjAC = adjA && adjC
    adjBD = adjB && adjD

    if adjAC
       AS, CS, Z1, Q1 = schur(A.parent,C.parent)
    else
       adjA && (A = copy(A))
       adjC && (C = copy(C))
       AS, CS, Q1, Z1 = schur(A,C)
    end
    if adjBD
       BS, DS, Z2, Q2 = schur(B.parent,D.parent)
    else
       adjB && (B = copy(B))
       adjD && (D = copy(D))
       BS, DS, Q2, Z2 = schur(B,D)
    end

    Y = Q1' * E *Z2

    gsylvs!(AS, BS, CS, DS, Y, adjAC = adjAC, adjBD = adjBD)

    mul!(Y, Z1, Y*Q2')

   return Y
end
# solve AXB = C
gsylv(A::Union{AbstractMatrix,UniformScaling,Real,Complex},B::Union{AbstractMatrix,UniformScaling,Real,Complex},E::AbstractMatrix) = (A\E)/B
# solve AXB+γX = E
gsylv(A::AbstractMatrix,B::AbstractMatrix,C::Union{UniformScaling,Real,Complex},E::AbstractMatrix) =
size(A,1) == size(A,2) && size(B,1) == size(B,2) ? gsylv(A,B,Matrix{eltype(C)}(C*I,size(A)),Matrix{eltype(C)}(I,size(B)),E) :
throw(DimensionMismatch("A and B must be square matrices"))
# solve AXB+γXδ = E
gsylv(A::AbstractMatrix,B::AbstractMatrix,C::Union{UniformScaling,Real,Complex},D::Union{UniformScaling,Real,Complex},E::AbstractMatrix) = gsylv(A,B,C*D,E)
# solve AXB+γXD = E
gsylv(A::AbstractMatrix,B::AbstractMatrix,C::Union{UniformScaling,Real,Complex},D::AbstractMatrix,E::AbstractMatrix) =
size(A,1) == size(A,2) ? gsylv(A,B,Matrix{eltype(C)}(C*I,size(A)),D,E) :
throw(DimensionMismatch("A must be a square matrix"))
# solve AXB+CXδ = E
gsylv(A::AbstractMatrix,B::AbstractMatrix,C::AbstractMatrix,D::Union{UniformScaling,Real,Complex},E::AbstractMatrix) =
size(B,1) == size(B,2) ? gsylv(A,B,C,Matrix{eltype(D)}(D*I,size(B)),E) :
throw(DimensionMismatch("B must be a square matrix"))

"""
    (X,Y) = sylvsys(A,B,C,D,E,F)

Solve the Sylvester system of matrix equations

                AX + YB = C
                DX + YE = F,

where `(A,D)`, `(B,E)` are pairs of square matrices of the same size.
The pencils `A-λD` and `-B+λE` must be regular and must not have common eigenvalues.
# Example
```jldoctest
julia> A = [3. 4.; 5. 6.]
2×2 Array{Float64,2}:
 3.0  4.0
 5.0  6.0

julia> B = [1. 1.; 1. 2.]
2×2 Array{Float64,2}:
 1.0  1.0
 1.0  2.0

julia> C = [-1. -2.; 2. -1.]
2×2 Array{Float64,2}:
 -1.0  -2.0
  2.0  -1.0

julia> D = [1. -2.; -2. -1.]
2×2 Array{Float64,2}:
  1.0  -2.0
 -2.0  -1.0

julia> E = [1. -1.; -2. 2.]
2×2 Array{Float64,2}:
  1.0  -1.0
 -2.0   2.0

julia> F = [1. -1.; -2. 2.]
2×2 Array{Float64,2}:
  1.0  -1.0
 -2.0   2.0

julia> X, Y = sylvsys(A, B, C, D, E, F);

julia> X
2×2 Array{Float64,2}:
  1.388  -1.388
 -0.892   0.892

julia> Y
2×2 Array{Float64,2}:
 -1.788  0.192
  0.236  0.176

julia> A*X + Y*B - C
2×2 Array{Float64,2}:
  6.66134e-16  2.22045e-15
 -3.10862e-15  2.66454e-15

julia> D*X + Y*E - F
2×2 Array{Float64,2}:
  1.33227e-15  -2.22045e-15
 -4.44089e-16   4.44089e-16
```
"""
function sylvsys(A::AbstractMatrix,B::AbstractMatrix,C::AbstractMatrix,D::AbstractMatrix,E::AbstractMatrix,F::AbstractMatrix)

    m, n = size(C);
    (m == size(F,1) && n == size(F,2)) ||
      throw(DimensionMismatch("C and F must have the same dimensions"))
    [m; n; m; n] == LinearAlgebra.checksquare(A,B,D,E) ||
       throw(DimensionMismatch("A, B, C, D, E and F have incompatible dimensions"))
    T2 = promote_type(eltype(A), eltype(B), eltype(C), eltype(D), eltype(E), eltype(F))
    T2 <: BlasFloat || (T2 = promote_type(Float64,T2))
    eltype(A) == T2 || (A = convert(Matrix{T2},A))
    eltype(B) == T2 || (B = convert(Matrix{T2},B))
    eltype(C) == T2 || (C = convert(Matrix{T2},C))
    eltype(D) == T2 || (D = convert(Matrix{T2},D))
    eltype(E) == T2 || (E = convert(Matrix{T2},E))
    eltype(F) == T2 || (F = convert(Matrix{T2},F))

    isa(A,Adjoint) && (A = copy(A))
    isa(B,Adjoint) && (B = copy(B))
    isa(D,Adjoint) && (D = copy(D))
    isa(E,Adjoint) && (E = copy(E))

    AS, DS, Q1, Z1 = schur(A,D)
    BS, ES, Q2, Z2 = schur(B,E)

    CS = adjoint(Q1) * (C*Z2)
    FS = adjoint(Q1) * (F*Z2)

    X, Y, scale =  tgsyl!('N',AS,BS,CS,DS,ES,FS)

    (rmul!(Z1*(X * adjoint(Z2)), inv(scale)), rmul!(Q1*(Y * adjoint(Q2)), inv(-scale)) )
end
"""
    (X,Y) = dsylvsys(A,B,C,D,E,F)

Solve the dual Sylvester system of matrix equations

       AX + DY = C
       XB + YE = F ,

where `(A,D)`, `(B,E)` are pairs of square matrices of the same size.
The pencils `A-λD` and `-B+λE` must be regular and must not have common eigenvalues.

# Example
```jldoctest
julia> A = [3. 4.; 5. 6.]
2×2 Array{Float64,2}:
 3.0  4.0
 5.0  6.0

julia> B = [1. 1.; 1. 2.]
2×2 Array{Float64,2}:
 1.0  1.0
 1.0  2.0

julia> C = [-1. -2.; 2. -1.]
2×2 Array{Float64,2}:
 -1.0  -2.0
  2.0  -1.0

julia> D = [1. -2.; -2. -1.]
2×2 Array{Float64,2}:
  1.0  -2.0
 -2.0  -1.0

julia> E = [1. -1.; -2. 2.]
2×2 Array{Float64,2}:
  1.0  -1.0
 -2.0   2.0

julia> F = [1. -1.; -2. 2.]
2×2 Array{Float64,2}:
  1.0  -1.0
 -2.0   2.0

julia> X, Y = dsylvsys(A, B, C, D, E, F);

julia> X
2×2 Array{Float64,2}:
  2.472  -1.648
 -1.848   1.232

julia> Y
2×2 Array{Float64,2}:
 -0.496  -0.336
  0.264   0.824

julia> A*X + D*Y - C
2×2 Array{Float64,2}:
  4.44089e-16  0.0
 -3.55271e-15  1.55431e-15

julia> X*B + Y*E - F
2×2 Array{Float64,2}:
 -8.88178e-16   0.0
  8.88178e-16  -4.44089e-16
```
"""
function dsylvsys(A::AbstractMatrix,B::AbstractMatrix,C::AbstractMatrix,D::AbstractMatrix,E::AbstractMatrix,F::AbstractMatrix)

    m, n = size(C);
    (m == size(F,1) && n == size(F,2)) ||
      throw(DimensionMismatch("C and F must have the same dimensions"))
    [m; n; m; n] == LinearAlgebra.checksquare(A,B,D,E) ||
       throw(DimensionMismatch("A, B, C, D, E and F have incompatible dimensions"))

    T2 = promote_type(eltype(A), eltype(B), eltype(C), eltype(D), eltype(E), eltype(F))
    T2 <: BlasFloat || (T2 = promote_type(Float64,T2))
    eltype(A) == T2 || (A = convert(Matrix{T2},A))
    eltype(B) == T2 || (B = convert(Matrix{T2},B))
    eltype(C) == T2 || (C = convert(Matrix{T2},C))
    eltype(D) == T2 || (D = convert(Matrix{T2},D))
    eltype(E) == T2 || (E = convert(Matrix{T2},E))
    eltype(F) == T2 || (F = convert(Matrix{T2},F))
    realcase = T2 <: AbstractFloat
    transsylv = isa(A,Adjoint) && isa(B,Adjoint) && isa(D,Adjoint) && isa(E,Adjoint)
    realcase ? trans = 'T' : trans = 'C'
    if transsylv
       AS, DS, Q1, Z1 = schur(A.parent,D.parent)
       BS, ES, Q2, Z2 = schur(B.parent,E.parent)
       CS = adjoint(Z1) * (C*Z2)
       FS = adjoint(Q1) * (F*Q2)
       X, Y, scale =  tgsyl!(trans,AS,BS,CS,DS,ES,-FS)
       (rmul!(Q1*(X * adjoint(Z2)), inv(scale)), rmul!(Q1*(Y * adjoint(Z2)), inv(scale)) )
    else
       AS, DS, Q1, Z1 = schur(copy(A'),copy(D'))
       BS, ES, Q2, Z2 = schur(copy(B'),copy(E'))
       CS = adjoint(Z1) * (C*Z2)
       FS = adjoint(Q1) * (F*Q2)

       X, Y, scale =  tgsyl!(trans,AS,BS,CS,DS,ES,-FS)

       (rmul!(Q1*(X * adjoint(Z2)), inv(scale)), rmul!(Q1*(Y * adjoint(Z2)), inv(scale)) )
    end
end
"""
    sylvcs!(A,B,C; adjA = false, adjB = false)

Solve the continuous Sylvester matrix equation

                op(A)X + Xop(B) =  C,

where `op(A) = A` or `op(A) = A'` if `adjA = false` or `adjA = true`, respectively,
and `op(B) = B` or `op(B) = B'` if `adjB = false` or `adjB = true`, respectively.
`A` and `B` are square matrices in Schur forms, and `A` and `-B` must not have
common eigenvalues. `C` contains on output the solution `X`.
"""
function sylvcs!(A::AbstractMatrix{T1}, B::AbstractMatrix{T1}, C::AbstractMatrix{T1}; adjA::Bool = false, adjB::Bool = false) where  T1<:BlasFloat
   """
   This is a wrapper to the LAPACK.trsylv! function, based on the Bartels-Stewart Schur form based approach.
   Reference:
   R. H. Bartels and G. W. Stewart. Algorithm 432: Solution of the matrix equation AX+XB=C.
   Comm. ACM, 15:820–826, 1972.
   """
   try
      trans = T1 <: Complex ? 'C' : 'T'
      C, scale = LAPACK.trsyl!(adjA ? trans : 'N', adjB ? trans : 'N', A, B, C)
      rmul!(C, inv(scale))
      return C[:,:]
   catch err
      findfirst("LAPACKException(1)",string(err)) === nothing ? rethrow() :
               throw("ME:SingularException: A has eigenvalue(s) α and B has eigenvalues(s) β such that α+β = 0")
   end
end
function sylvd2!(adjA::Bool, adjB::Bool, C::AbstractMatrix{T}, na::Int, nb::Int, A::AbstractMatrix{T}, B::AbstractMatrix{T}, Xw::AbstractMatrix{T}) where {T <:BlasReal}
   # speed and reduced allocation oriented implementation of a solver for 1x1 and 2x2 Sylvester equations
   # encountered in solving discrete Lyapunov equations:
   # A*X*B + X = C   if adjA = false and adjB = false -> R = kron(B',A) + I
   # A'*X*B + X = C  if adjA = true  and adjB = false -> R = kron(B',A') + I
   # A*X*B' + X = C  if adjA = false and adjB = true  -> R = kron(B,A) + I
   # A'*X*B' + X = C if adjA = true  and adjB = true  -> R = kron(B,A') + I
   ONE = one(T)
   if na == 1 && nb == 1
      temp = A[1,1]*B[1,1] + ONE
      rmul!(C,inv(temp))
      any(!isfinite, C) && throw("ME:SingularException: A has eigenvalue(s) α and B has eigenvalues(s) β such that αβ = -1")
      return C
   end
   n = na*nb
   i1 = 1:n
   R = view(Xw, i1, i1)
   Y = reshape(C, n)
   if adjA && !adjB
      if na == 1
         # R12 =
         # [ a11*b11+1      a11*b21]
         # [     a11*b12  a11*b22+1]
         # @inbounds R = [ A[1,1]*B[1,1]+ONE      A[1,1]*B[2,1];
         #                 A[1,1]*B[1,2]  A[1,1]*B[2,2]+ONE]
         @inbounds  R[1,1] = A[1,1]*B[1,1]+ONE
         @inbounds  R[1,2] = A[1,1]*B[2,1]
         @inbounds  R[2,1] = A[1,1]*B[1,2]
         @inbounds  R[2,2] = A[1,1]*B[2,2]+ONE
         # @inbounds  Y[1] = C[1,1]
         # @inbounds  Y[2] = C[1,2]
      else
         if nb == 1
            # R21 =
            # [ a11*b11+1      a21*b11]
            # [     a12*b11  a22*b11+1]
            # @inbounds R = [ A[1,1]*B[1,1]+ONE      A[2,1]*B[1,1];
            #                 A[1,2]*B[1,1]  A[2,2]*B[1,1]+ONE ]
            @inbounds  R[1,1] = A[1,1]*B[1,1]+ONE
            @inbounds  R[1,2] = A[2,1]*B[1,1]
            @inbounds  R[2,1] = A[1,2]*B[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+ONE
            # @inbounds  Y[1] = C[1,1]
            # @inbounds  Y[2] = C[2,1]
         else
            # R =
            # [ a11*b11+1      a21*b11      a11*b21      a21*b21]
            # [     a12*b11  a22*b11+1      a12*b21      a22*b21]
            # [     a11*b12      a21*b12  a11*b22+1      a21*b22]
            # [     a12*b12      a22*b12      a12*b22  a22*b22+1]
            # @inbounds R = [ A[1,1]*B[1,1]+ONE      A[2,1]*B[1,1]      A[1,1]*B[2,1]      A[2,1]*B[2,1];
            # A[1,2]*B[1,1]  A[2,2]*B[1,1]+ONE      A[1,2]*B[2,1]      A[2,2]*B[2,1];
            # A[1,1]*B[1,2]      A[2,1]*B[1,2]  A[1,1]*B[2,2]+ONE      A[2,1]*B[2,2];
            # A[1,2]*B[1,2]      A[2,2]*B[1,2]      A[1,2]*B[2,2]  A[2,2]*B[2,2]+ONE]
            @inbounds  R[1,1] = A[1,1]*B[1,1]+ONE
            @inbounds  R[1,2] = A[2,1]*B[1,1]
            @inbounds  R[1,3] = A[1,1]*B[2,1]
            @inbounds  R[1,4] = A[2,1]*B[2,1]
            @inbounds  R[2,1] = A[1,2]*B[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+ONE
            @inbounds  R[2,3] = A[1,2]*B[2,1]
            @inbounds  R[2,4] = A[2,2]*B[2,1]
            @inbounds  R[3,1] = A[1,1]*B[1,2]
            @inbounds  R[3,2] = A[2,1]*B[1,2]
            @inbounds  R[3,3] = A[1,1]*B[2,2]+ONE
            @inbounds  R[3,4] = A[2,1]*B[2,2]
            @inbounds  R[4,1] = A[1,2]*B[1,2]
            @inbounds  R[4,2] = A[2,2]*B[1,2]
            @inbounds  R[4,3] = A[1,2]*B[2,2]
            @inbounds  R[4,4] = A[2,2]*B[2,2]+ONE
            # @inbounds  Y[1] = C[1,1]
            # @inbounds  Y[2] = C[2,1]
            # @inbounds  Y[3] = C[1,2]
            # @inbounds  Y[4] = C[2,2]
         end
      end
      #R = kron(transpose(B),A) + I
   elseif !adjA && adjB
      if na == 1
         # R12 =
         # [ a11*b11+1      a11*b12]
         # [     a11*b21  a11*b22+1]
         # @inbounds R = [ A[1,1]*B[1,1]+ONE      A[1,1]*B[1,2];
         #                 A[1,1]*B[2,1]  A[1,1]*B[2,2]+ONE]
         @inbounds  R[1,1] = A[1,1]*B[1,1]+ONE
         @inbounds  R[1,2] = A[1,1]*B[1,2]
         @inbounds  R[2,1] = A[1,1]*B[2,1]
         @inbounds  R[2,2] = A[1,1]*B[2,2]+ONE
         # @inbounds  Y[1] = C[1,1]
         # @inbounds  Y[2] = C[1,2]
      else
         if nb == 1
            # R21 =
            #    [ a11*b11+1      a12*b11]
            #    [     a21*b11  a22*b11+1]
            # @inbounds R = [ A[1,1]*B[1,1]+ONE      A[1,2]*B[1,1];
            #                 A[2,1]*B[1,1]  A[2,2]*B[1,1]+ONE]
            @inbounds  R[1,1] = A[1,1]*B[1,1]+ONE
            @inbounds  R[1,2] = A[1,2]*B[1,1]
            @inbounds  R[2,1] = A[2,1]*B[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+ONE
            # @inbounds  Y[1] = C[1,1]
            # @inbounds  Y[2] = C[2,1]
         else
            # R =
            # [ a11*b11+1      a12*b11      a11*b12      a12*b12]
            # [     a21*b11  a22*b11+1      a21*b12      a22*b12]
            # [     a11*b21      a12*b21  a11*b22+1      a12*b22]
            # [     a21*b21      a22*b21      a21*b22  a22*b22+1]
            # @inbounds R = [ A[1,1]*B[1,1]+ONE      A[1,2]*B[1,1]      A[1,1]*B[1,2]      A[1,2]*B[1,2];
            # A[2,1]*B[1,1]  A[2,2]*B[1,1]+ONE      A[2,1]*B[1,2]      A[2,2]*B[1,2];
            # A[1,1]*B[2,1]      A[1,2]*B[2,1]  A[1,1]*B[2,2]+ONE      A[1,2]*B[2,2];
            # A[2,1]*B[2,1]      A[2,2]*B[2,1]      A[2,1]*B[2,2]  A[2,2]*B[2,2]+ONE]
            @inbounds  R[1,1] = A[1,1]*B[1,1]+ONE
            @inbounds  R[1,2] = A[1,2]*B[1,1]
            @inbounds  R[1,3] = A[1,1]*B[1,2]
            @inbounds  R[1,4] = A[1,2]*B[1,2]
            @inbounds  R[2,1] = A[2,1]*B[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+ONE
            @inbounds  R[2,3] = A[2,1]*B[1,2]
            @inbounds  R[2,4] = A[2,2]*B[1,2]
            @inbounds  R[3,1] = A[1,1]*B[2,1]
            @inbounds  R[3,2] = A[1,2]*B[2,1]
            @inbounds  R[3,3] = A[1,1]*B[2,2]+ONE
            @inbounds  R[3,4] = A[1,2]*B[2,2]
            @inbounds  R[4,1] = A[2,1]*B[2,1]
            @inbounds  R[4,2] = A[2,2]*B[2,1]
            @inbounds  R[4,3] = A[2,1]*B[2,2]
            @inbounds  R[4,4] = A[2,2]*B[2,2]+ONE
            # @inbounds  Y[1] = C[1,1]
            # @inbounds  Y[2] = C[2,1]
            # @inbounds  Y[3] = C[1,2]
            # @inbounds  Y[4] = C[2,2]
         end
      end
      #R = kron(transpose(B),transpose(A)) + I
   elseif !adjA && !adjB
      if na == 1
         # R12 =
         # [ a11*b11 + 1,     a11*b21]
         # [     a11*b12, a11*b22 + 1]
         # @inbounds R = [ A[1,1]*B[1,1]+ONE      A[1,1]*B[2,1];
         #                 A[1,1]*B[1,2]  A[1,1]*B[2,2]+ONE]
         @inbounds  R[1,1] = A[1,1]*B[1,1]+ONE
         @inbounds  R[1,2] = A[1,1]*B[2,1]
         @inbounds  R[2,1] = A[1,1]*B[1,2]
         @inbounds  R[2,2] = A[1,1]*B[2,2]+ONE
         # @inbounds  Y[1] = C[1,1]
         # @inbounds  Y[2] = C[1,2]
      else
         if nb == 1
            # R21 =
            # [ a11*b11 + 1,     a12*b11]
            # [     a21*b11, a22*b11 + 1]
            # @inbounds R = [ A[1,1]*B[1,1]+ONE      A[1,2]*B[1,1];
            #                 A[2,1]*B[1,1]  A[2,2]*B[1,1]+ONE]
            @inbounds  R[1,1] = A[1,1]*B[1,1]+ONE
            @inbounds  R[1,2] = A[1,2]*B[1,1]
            @inbounds  R[2,1] = A[2,1]*B[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+ONE
            # @inbounds  Y[1] = C[1,1]
            # @inbounds  Y[2] = C[2,1]
         else
            # R =
            # [ a11*b11 + 1,     a12*b11,     a11*b21,     a12*b21]
            # [     a21*b11, a22*b11 + 1,     a21*b21,     a22*b21]
            # [     a11*b12,     a12*b12, a11*b22 + 1,     a12*b22]
            # [     a21*b12,     a22*b12,     a21*b22, a22*b22 + 1]
            # @inbounds R = [ A[1,1]*B[1,1]+ONE      A[1,2]*B[1,1]      A[1,1]*B[2,1]      A[1,2]*B[2,1];
            # A[2,1]*B[1,1]  A[2,2]*B[1,1]+ONE      A[2,1]*B[2,1]      A[2,2]*B[2,1];
            # A[1,1]*B[1,2]      A[1,2]*B[1,2]  A[1,1]*B[2,2]+ONE      A[1,2]*B[2,2];
            # A[2,1]*B[1,2]      A[2,2]*B[1,2]      A[2,1]*B[2,2]  A[2,2]*B[2,2]+ONE]
            @inbounds  R[1,1] = A[1,1]*B[1,1]+ONE
            @inbounds  R[1,2] = A[1,2]*B[1,1]
            @inbounds  R[1,3] = A[1,1]*B[2,1]
            @inbounds  R[1,4] = A[1,2]*B[2,1]
            @inbounds  R[2,1] = A[2,1]*B[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+ONE
            @inbounds  R[2,3] = A[2,1]*B[2,1]
            @inbounds  R[2,4] = A[2,2]*B[2,1]
            @inbounds  R[3,1] = A[1,1]*B[1,2]
            @inbounds  R[3,2] = A[1,2]*B[1,2]
            @inbounds  R[3,3] = A[1,1]*B[2,2]+ONE
            @inbounds  R[3,4] = A[1,2]*B[2,2]
            @inbounds  R[4,1] = A[2,1]*B[1,2]
            @inbounds  R[4,2] = A[2,2]*B[1,2]
            @inbounds  R[4,3] = A[2,1]*B[2,2]
            @inbounds  R[4,4] = A[2,2]*B[2,2]+ONE
            # @inbounds  Y[1] = C[1,1]
            # @inbounds  Y[2] = C[2,1]
            # @inbounds  Y[3] = C[1,2]
            # @inbounds  Y[4] = C[2,2]
         end
      end
      #R = kron(B,A) + I
   else
      if na == 1
         # R12 =
         # [ a11*b11 + 1,     a11*b12]
         # [     a11*b21, a11*b22 + 1]
         # @inbounds R = [ A[1,1]*B[1,1]+ONE      A[1,1]*B[1,2];
         #                 A[1,1]*B[2,1]  A[1,1]*B[2,2]+ONE]
         @inbounds  R[1,1] = A[1,1]*B[1,1]+ONE
         @inbounds  R[1,2] = A[1,1]*B[1,2]
         @inbounds  R[2,1] = A[1,1]*B[2,1]
         @inbounds  R[2,2] = A[1,1]*B[2,2]+ONE
         # @inbounds  Y[1] = C[1,1]
         # @inbounds  Y[2] = C[1,2]
      else
         if nb == 1
            # R21 =
            # [ a11*b11 + 1,     a21*b11]
            # [     a12*b11, a22*b11 + 1]
            # @inbounds R = [ A[1,1]*B[1,1]+ONE      A[2,1]*B[1,1];
            #                 A[1,2]*B[1,1]  A[2,2]*B[1,1]+ONE]
            @inbounds  R[1,1] = A[1,1]*B[1,1]+ONE
            @inbounds  R[1,2] = A[2,1]*B[1,1]
            @inbounds  R[2,1] = A[1,2]*B[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+ONE
            # @inbounds  Y[1] = C[1,1]
            # @inbounds  Y[2] = C[2,1]
         else
            # R =
            # [ a11*b11 + 1,     a21*b11,     a11*b12,     a21*b12]
            # [     a12*b11, a22*b11 + 1,     a12*b12,     a22*b12]
            # [     a11*b21,     a21*b21, a11*b22 + 1,     a21*b22]
            # [     a12*b21,     a22*b21,     a12*b22, a22*b22 + 1]
            # @inbounds R = [ A[1,1]*B[1,1]+ONE      A[2,1]*B[1,1]      A[1,1]*B[1,2]      A[2,1]*B[1,2];
            # A[1,2]*B[1,1]  A[2,2]*B[1,1]+ONE      A[1,2]*B[1,2]      A[2,2]*B[1,2];
            # A[1,1]*B[2,1]      A[2,1]*B[2,1]  A[1,1]*B[2,2]+ONE      A[2,1]*B[2,2];
            # A[1,2]*B[2,1]      A[2,2]*B[2,1]      A[1,2]*B[2,2]  A[2,2]*B[2,2]+ONE]
            @inbounds  R[1,1] = A[1,1]*B[1,1]+ONE
            @inbounds  R[1,2] = A[2,1]*B[1,1]
            @inbounds  R[1,3] = A[1,1]*B[1,2]
            @inbounds  R[1,4] = A[2,1]*B[1,2]
            @inbounds  R[2,1] = A[1,2]*B[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+ONE
            @inbounds  R[2,3] = A[1,2]*B[1,2]
            @inbounds  R[2,4] = A[2,2]*B[1,2]
            @inbounds  R[3,1] = A[1,1]*B[2,1]
            @inbounds  R[3,2] = A[2,1]*B[2,1]
            @inbounds  R[3,3] = A[1,1]*B[2,2]+ONE
            @inbounds  R[3,4] = A[2,1]*B[2,2]
            @inbounds  R[4,1] = A[1,2]*B[2,1]
            @inbounds  R[4,2] = A[2,2]*B[2,1]
            @inbounds  R[4,3] = A[1,2]*B[2,2]
            @inbounds  R[4,4] = A[2,2]*B[2,2]+ONE
            # @inbounds  Y[1] = C[1,1]
            # @inbounds  Y[2] = C[2,1]
            # @inbounds  Y[3] = C[1,2]
            # @inbounds  Y[4] = C[2,2]
         end
         #R = kron(B,transpose(A)) + I
      end
   end
   luslv!(R,Y) && throw("ME:SingularException: A has eigenvalue(s) α and B has eingenvalu(s) β such that αβ = -1")
   #C[:,:] = Y
   return C
end
"""
    sylvds!(A,B,C; adjA = false, adjB = false)

Solve the discrete Sylvester matrix equation

                op(A)Xop(B) + X =  C,

where `op(A) = A` or `op(A) = A'` if `adjA = false` or `adjA = true`, respectively,
and `op(B) = B` or `op(B) = B'` if `adjB = false` or `adjB = true`, respectively.
`A` and `B` are square matrices in Schur forms, and `A` and `-B` must not have
common reciprocal eigenvalues. `C` contains on output the solution `X`.
"""
function sylvds!(A::AbstractMatrix{T1}, B::AbstractMatrix{T1}, C::AbstractMatrix{T1}, W::AbstractMatrix{T1} = similar(A,size(A,1),2); adjA::Bool = false, adjB::Bool = false) where  T1<:BlasReal
   """
   An extension of the Bartels-Stewart Schur form based approach is employed.

   Reference:
   R. H. Bartels and G. W. Stewart. Algorithm 432: Solution of the matrix equation AX+XB=C.
   Comm. ACM, 15:820–826, 1972.
   """
   m, n = LinearAlgebra.checksquare(A,B)
   (size(C,1) == m && size(C,2) == n ) || throw(DimensionMismatch("C must be an $m x $n matrix"))
   (m, 2) == size(W) || throw(DimensionMismatch("W must be an $m x 2 matrix"))
   ONE = one(T1)

   # determine the structure of the real Schur form of A
   ba, pa = sfstruct(A)
   bb, pb = sfstruct(B)

   G = Matrix{T1}(undef,2,2)
   WA = Matrix{T1}(undef,2,2)

   Xw = Matrix{T1}(undef,4,4)
   if !adjA && !adjB
      # """
      # The (K,L)th block of X is determined starting from
      # bottom-left corner column by column by

      #            A(K,K)*X(K,L)*B(L,L) + X(K,L) = C(K,L) - R(K,L)

      # where
      #                        M
      #            R(K,L) = { SUM [A(K,J)*X(J,L)] } * B(L,L) +
      #                      J=K+1
      #                        M             L-1
      #                       SUM { A(K,J) * SUM [X(J,I)*B(I,L)] }.
      #                       J=K            I=1
      # """
      j = 1
      for ll = 1:pb
          dl = bb[ll]
          dll = 1:dl
          il1 = 1:j-1
          j1 = j+dl-1
          l = j:j1
          i = m
          for kk = pa:-1:1
              dk = ba[kk]
              dkk = 1:dk
              i1 = i-dk+1
              k = i1:i
              Ckl = view(C,k,l)
              y = view(G,1:dk,1:dl)
              copyto!(y,Ckl)
              if kk < pa
                 ir = i+1:m
                 W1 = view(WA,dkk,dll)
                 mul!(W1,view(A,k,ir),view(C,ir,l))
                 mul!(y,W1,view(B,l,l),-ONE,ONE)
              end
              if ll > 1
                 ic = i1:m
                 mul!(view(W,k,dll),view(C,k,il1),view(B,il1,l))
                 mul!(y,view(A,k,ic),view(W,ic,dll),-ONE,ONE)
              end
              sylvd2!(adjA,adjB,y,dk,dl,view(A,k,k),view(B,l,l),Xw)
              copyto!(Ckl,y)
           i -= dk
          end
          j += dl
      end
   elseif !adjA && adjB
         # """
         # The (K,L)th block of X is determined starting from
         # bottom-right corner column by column by

         #             A(K,K)*X(K,L)*B(L,L)' + X(K,L) = C(K,L) - R(K,L)

         # where
         #                        M
         #            R(K,L) = { SUM [A(K,J)*X(J,L)] } * B(L,L)' +
         #                      J=K+1
         #                        M              N
         #                       SUM { A(K,J) * SUM [X(J,I)*B(L,I)'] }.
         #                       J=K           I=L+1
         # """
         j = n
         for ll = pb:-1:1
             dl = bb[ll]
             dll = 1:dl
             il1 = j+1:n
             l = j-dl+1:j
             i = m
             for kk = pa:-1:1
                 dk = ba[kk]
                 dkk = 1:dk
                 i1 = i-dk+1
                 k = i1:i
                 Ckl = view(C,k,l)
                 y = view(G,1:dk,1:dl)
                 copyto!(y,Ckl)
                 if kk < pa
                    ir = i+1:m
                    W1 = view(WA,dkk,dll)
                    mul!(W1,view(A,k,ir),view(C,ir,l))
                    mul!(y,W1,adjoint(view(B,l,l)),-ONE,ONE)
                 end
                 if ll < pb
                    ic = i1:m
                    mul!(view(W,k,dll),view(C,k,il1),adjoint(view(B,l,il1)))
                    mul!(y,view(A,k,ic),view(W,ic,dll),-ONE,ONE)
                 end
                 sylvd2!(adjA,adjB,y,dk,dl,view(A,k,k),view(B,l,l),Xw)
                 copyto!(Ckl,y)
                 i -= dk
             end
             j -= dl
         end
   elseif adjA && !adjB
      # """
      # The (K,L)th block of X is determined starting from the
      # upper-left corner column by column by

      # A(K,K)'*X(K,L)*B(L,L) + X(K,L) = C(K,L) - R(K,L),

      # where
      #                       K-1
      #            R(K,L) = { SUM [A(J,K)'*X(J,L)] } * B(L,L) +
      #                       J=1
      #                        K              L-1
      #                       SUM A(J,K)' * { SUM [X(J,I)*B(I,L)] }.
      #                       J=1             I=1
      # """
      j = 1
      for ll = 1:pb
          dl = bb[ll]
          dll = 1:dl
          il1 = 1:j-1
          j1 = j+dl-1
          l = j:j1
          i = 1
          for kk = 1:pa
              dk = ba[kk]
              dkk = 1:dk
              i1 = i+dk-1
              k = i:i1
              Ckl = view(C,k,l)
              y = view(G,1:dk,1:dl)
              copyto!(y,Ckl)
              if kk > 1
                 ir = 1:i-1
                 W1 = view(WA,dkk,dll)
                 mul!(W1,adjoint(view(A,ir,k)),view(C,ir,l))
                 mul!(y,W1,view(B,l,l),-ONE,ONE)
              end
              if ll > 1
                 ic = 1:i1
                 mul!(view(W,k,dll),view(C,k,il1),view(B,il1,l))
                 mul!(y,adjoint(view(A,ic,k)),view(W,ic,dll),-ONE,ONE)
              end
              sylvd2!(adjA,adjB,y,dk,dl,view(A,k,k),view(B,l,l),Xw)
              copyto!(Ckl,y)
           i += dk
          end
          j += dl
      end
   elseif adjA && adjB
      # """
      # The (K,L)th block of X is determined starting from the
      # lower-left corner column by column by

      #            A(K,K)'*X(K,L)*B(L,L)' + X(K,L) = C(K,L) - R(K,L)

      # where
      #                       K-1
      #            R(K,L) = { SUM [A(J,K)'*X(J,L)] } * B(L,L)' +
      #                       J=1
      #                        K               N
      #                       SUM A(J,K)' * { SUM [X(J,I)*B(L,I)'] }.
      #                       J=1            I=L+1
      # """
      j = n
      for ll = pb:-1:1
          dl = bb[ll]
          dll = 1:dl
          il1 = j+1:n
          l = j-dl+1:j
          i = 1
          for kk = 1:pa
              dk = ba[kk]
              dkk = 1:dk
              i1 = i+dk-1
              k = i:i1
              Ckl = view(C,k,l)
              y = view(G,1:dk,1:dl)
              copyto!(y,Ckl)
              if kk > 1
                 ir = 1:i-1
                 W1 = view(WA,dkk,dll)
                 mul!(W1,adjoint(view(A,ir,k)),view(C,ir,l))
                 mul!(y,W1,adjoint(view(B,l,l)),-ONE,ONE)
              end
              if ll < pb
                 ic = 1:i1
                 mul!(view(W,k,dll),view(C,k,il1),adjoint(view(B,l,il1)))
                 mul!(y,adjoint(view(A,ic,k)),view(W,ic,dll),-ONE,ONE)
              end
              sylvd2!(adjA,adjB,y,dk,dl,view(A,k,k),view(B,l,l),Xw)
              copyto!(Ckl,y)
           i += dk
          end
          j -= dl
      end
   end
   return C
end
function sylvds!(A::AbstractMatrix{T1}, B::AbstractMatrix{T1}, C::AbstractMatrix{T1}, W::AbstractVector{T1} = similar(A,size(A,1)); adjA::Bool = false, adjB::Bool = false) where  T1<:BlasComplex
   """
   An extension of the Bartels-Stewart Schur form based approach is employed.

   Reference:
   R. H. Bartels and G. W. Stewart. Algorithm 432: Solution of the matrix equation AX+XB=C.
   Comm. ACM, 15:820–826, 1972.
   """
   m, n = LinearAlgebra.checksquare(A,B)
   (size(C,1) == m && size(C,2) == n ) || throw(DimensionMismatch("C must be an $m x $n matrix"))

   ONE = one(T1)
   ZERO = zero(T1)
   if !adjA && !adjB
      # """
      # The (K,L)th element of X is determined starting from
      # bottom-left corner column by column by

      #            A(K,K)*X(K,L)*B(L,L) + X(K,L) = C(K,L) - R(K,L)

      # where
      #                        M
      #            R(K,L) = { SUM [A(K,J)*X(J,L)] } * B(L,L) +
      #                      J=K+1
      #                        M             L-1
      #                       SUM { A(K,J) * SUM [X(J,I)*B(I,L)] }.
      #                       J=K            I=1
      # """
      for l = 1:n
          il1 = 1:l-1
          for k = m:-1:1
              y = C[k,l]
              if k < m
                 ta = ZERO
                 for ir = k+1:m
                     ta += A[k,ir]*C[ir,l]
                 end
                 y -= ta*B[l,l]
              end
              if l > 1
                 tz = ZERO
                 for ir = il1
                     tz += C[k,ir]*B[ir,l]
                 end
                 W[k] = tz
                 for ic = k:m
                     y -= A[k,ic]*W[ic]
                 end
              end
              C[k,l] = y/(B[l,l]*A[k,k]+ONE)
              isfinite(C[k,l]) || throw("ME:SingularException: A and -B have common or close reciprocal eigenvalues")
             end
      end
   elseif !adjA && adjB
         # """
         # The (K,L)th element of X is determined starting from
         # bottom-right corner column by column by

         #          A(K,K)*X(K,L)*B(L,L)' + X(K,L) = C(K,L) - R(K,L)

         # where
         #                        M
         #            R(K,L) = { SUM [A(K,J)*X(J,L)] } * B(L,L)' +
         #                      J=K+1
         #                        M              N
         #                       SUM { A(K,J) * SUM [X(J,I)*B(L,I)'] }.
         #                       J=K           I=L+1
         # """
         for l = n:-1:1
             il1 = l+1:n
             for k = m:-1:1
                 y = C[k,l]
                 if k < m
                    ta = ZERO
                    for ir = k+1:m
                       ta += A[k,ir]*C[ir,l]
                    end
                    y -= ta*B[l,l]'
                 end
                 if l < n
                    tz = ZERO
                    for ir = il1
                        tz += C[k,ir]*B[l,ir]'
                    end
                    W[k] = tz
                    for ic = k:m
                        y -= A[k,ic]*W[ic]
                    end
                 end
                 C[k,l] = y/(B[l,l]'*A[k,k]+ONE)
                 isfinite(C[k,l]) || throw("ME:SingularException: A and -B have common or close reciprocal eigenvalues")
             end
         end
   elseif adjA && !adjB
      # """
      # The (K,L)th element of X is determined starting from the
      # upper-left corner column by column by

      #          A(K,K)'*X(K,L)*B(L,L) + X(K,L) = C(K,L) - R(K,L),

      # where
      #                       K-1
      #            R(K,L) = { SUM [A(J,K)'*X(J,L)] } * B(L,L) +
      #                       J=1
      #                        K              L-1
      #                       SUM A(J,K)' * { SUM [X(J,I)*B(I,L)] }.
      #                       J=1             I=1
      # """
      for l = 1:n
          il1 = 1:l-1
          for k = 1:m
              y = C[k,l]
              if k > 1
                 ta = ZERO
                 for ir = 1:k-1
                     ta += A[ir,k]'*C[ir,l]
                 end
                 y -= ta*B[l,l]
              end
              if l > 1
                 ic = 1:m
                 tz = ZERO
                 for ir = il1
                     tz += C[k,ir]*B[ir,l]
                 end
                 W[k] = tz
                 for ic = 1:m
                     y -= A[ic,k]'*W[ic]
                 end
              end
              C[k,l] = y/(B[l,l]*A[k,k]'+ONE)
              isfinite(C[k,l]) || throw("ME:SingularException: A and -B have common or close reciprocal eigenvalues")
         end
      end
   elseif adjA && adjB
      # """
      # The (K,L)th element of X is determined starting from the
      # upper-right corner column by column by

      #         A(K,K)'*X(K,L)*B(L,L)' + X(K,L) = C(K,L) - R(K,L)

      # where
      #                       K-1
      #            R(K,L) = { SUM [A(J,K)'*X(J,L)] } * B(L,L)' +
      #                       J=1
      #                        K               N
      #                       SUM A(J,K)' * { SUM [X(J,I)*B(L,I)'] }.
      #                       J=1            I=L+1
      # """
      for l = n:-1:1
          il1 = l+1:n
          for k = 1:m
              y = C[k,l]
              if k > 1
                 ta = ZERO
                 for ir = 1:k-1
                     ta += A[ir,k]'*C[ir,l]
                 end
                 y -= ta*B[l,l]'
              end
              if l < n
                 ic = 1:m
                 tz = ZERO
                 for ir = il1
                     tz += C[k,ir]*B[l,ir]'
                 end
                 W[k] = tz
                 for ic = 1:m
                     y -= A[ic,k]'*W[ic]
                 end
              end
              C[k,l] = y/(B[l,l]'*A[k,k]'+ONE)
              isfinite(C[k,l]) || throw("ME:SingularException: A and -B have common or close reciprocal eigenvalues")
          end
      end
   end
   return C
end
"""
    X = gsylvs!(A,B,C,D,E; adjAC=false, adjBD=false, CASchur = false, DBSchur = false)

Solve the generalized Sylvester matrix equation

                op1(A)Xop2(B) + op1(C)Xop2(D) = E,

where `A`, `B`, `C` and `D` are square matrices, and

`op1(A) = A` and `op1(C) = C` if `adjAC = false`;

`op1(A) = A'` and `op1(C) = C'` if `adjAC = true`;

`op2(B) = B` and `op2(D) = D` if `adjBD = false`;

`op2(B) = B'` and `op2(D) = D'` if `adjBD = true`.

The matrix pair `(A,C)` is in a generalized real or complex Schur form.
The matrix pair `(B,D)` is in a generalized real or complex Schur form if `DBSchur = false`
or the matrix pair `(D,B)` is in a generalized real or complex Schur form if `DBSchur = true`.
The pencils `A-λC` and `D+λB` must be regular and must not have common eigenvalues.
"""
function gsylvs!(A::AbstractMatrix{T1}, B::AbstractMatrix{T1}, C::AbstractMatrix{T1}, D::AbstractMatrix{T1}, E::AbstractMatrix{T1},
                 WB::AbstractMatrix{T1} = similar(A,size(A,1),2), WD::AbstractMatrix{T1} = similar(A,size(A,1),2);
                 adjAC::Bool = false, adjBD::Bool = false, CASchur::Bool = false, DBSchur::Bool = false) where T1<:BlasReal
   """
   An extension proposed in [1] of the Bartels-Stewart Schur form based approach [2] is employed.

   References:
   [1] K.-W. E. Chu. The solution of the matrix equation AXB – CXD = E and
       (YA – DZ, YC– BZ) = (E, F). Lin. Alg. Appl., 93:93-105, 1987.
   [2] R. H. Bartels and G. W. Stewart. Algorithm 432: Solution of the matrix equation AX+XB=C.
       Comm. ACM, 15:820–826, 1972.
   """
   m, n = size(E);
   [m; n; m; n] == LinearAlgebra.checksquare(A,B,C,D) ||
      throw(DimensionMismatch("A, B, C, D and E have incompatible dimensions"))

   (m, 2) == size(WB) || throw(DimensionMismatch("WB must be an $m x 2 matrix"))
   (m, 2) == size(WD) || throw(DimensionMismatch("WD must be an $m x 2 matrix"))

   ONE = one(T1)

   # determine the structure of the generalized real Schur form of (A,C)
   CASchur ? ((ba, pa) = sfstruct(C)) : ((ba, pa) = sfstruct(A))
   DBSchur ? ((bb, pb) = sfstruct(D)) : ((bb, pb) = sfstruct(B))

   # WB = zeros(T1,m,2)
   # WD = zeros(T1,m,2)
   Xw = Matrix{T1}(undef,4,4)
   if !adjAC && !adjBD
      # """
      # The (K,L)th block of X is determined starting from
      # bottom-left corner column by column by

      #       A(K,K)*X(K,L)*B(L,L) + C(K,K)*X(K,L)*D(L,L) = E(K,L) - R(K,L)

      # where
      #                        M
      #            R(K,L) = { SUM [A(K,J)*X(J,L)] } * B(L,L) +
      #                      J=K+1
      #                        M             L-1
      #                       SUM { A(K,J) * SUM [X(J,I)*B(I,L)] } +
      #                       J=K            I=1

      #                        M
      #                     { SUM [C(K,J)*X(J,L)] } * D(L,L) +
      #                      J=K+1
      #                        M             L-1
      #                       SUM { C(K,J) * SUM [X(J,I)*D(I,L)] }.
      #                       J=K            I=1
      # """
      j = 1
      for ll = 1:pb
          dl = bb[ll]
          dll = 1:dl
          il1 = 1:j-1
          j1 = j+dl-1
          l = j:j1
          i = m
          for kk = pa:-1:1
              dk = ba[kk]
              dkk = 1:dk
              i1 = i-dk+1
              k = i1:i
              y = view(E,k,l)
              W1 = view(Xw,dkk,dll)
              if kk < pa
                 ir = i+1:m
                 #W1 = A[k,ir]*E[ir,l]
                 mul!(W1,view(A,k,ir),view(E,ir,l))
                 #y -= W1*B[l,l]
                 mul!(y,W1,view(B,l,l),-ONE,ONE)
                 #W1 = C[k,ir]*E[ir,l]
                 mul!(W1,view(C,k,ir),view(E,ir,l))
                 #y -= W1*D[l,l]
                 mul!(y,W1,view(D,l,l),-ONE,ONE)
              end
              if ll > 1
                 ic = i1:m
                 # WB[k,dll] = E[k,il1]*B[il1,l]
                 mul!(view(WB,k,dll),view(E,k,il1),view(B,il1,l))
                 # WD[k,dll] = E[k,il1]*D[il1,l]
                 mul!(view(WD,k,dll),view(E,k,il1),view(D,il1,l))
                 # y -= (A[k,ic]*WB[ic,dll] + C[k,ic]*WD[ic,dll])
                 mul!(y,view(A,k,ic),view(WB,ic,dll),-ONE,ONE)
                 mul!(y,view(C,k,ic),view(WD,ic,dll),-ONE,ONE)
              end
              gsylv2!(adjAC,adjBD,y,dk,dl,view(A,k,k),view(B,l,l),view(C,k,k),view(D,l,l),Xw)
              i -= dk
          end
          j += dl
      end
   elseif !adjAC && adjBD
         # """
         #  The (K,L)th block of X is determined starting from
         #  bottom-right corner column by column by

         #       A(K,K)*X(K,L)*B(L,L)' + C(K,K)*X(K,L)*D(L,L)' = E(K,L) - R(K,L)

         #  where
         #                        M
         #            R(K,L) = { SUM [A(K,J)*X(J,L)] } * B(L,L)' +
         #                      J=K+1
         #                        M              N
         #                       SUM { A(K,J) * SUM [X(J,I)*B(L,I)'] } +
         #                       J=K           I=L+1

         #                       M
         #                    { SUM [C(K,J)*X(J,L)] } * D(L,L)' +
         #                     J=K+1
         #                       M              N
         #                      SUM { C(K,J) * SUM [X(J,I)*D(L,I)'] }.
         #                      J=K           I=L+1
         # """
         j = n
         for ll = pb:-1:1
             dl = bb[ll]
             dll = 1:dl
             il1 = j+1:n
             l = j-dl+1:j
             i = m
             for kk = pa:-1:1
                 dk = ba[kk]
                 dkk = 1:dk
                 i1 = i-dk+1
                 k = i1:i
                 y = view(E,k,l)
                 W1 = view(Xw,dkk,dll)
                 if kk < pa
                    ir = i+1:m
                    #W1 = A[k,ir]*E[ir,l]
                    mul!(W1,view(A,k,ir),view(E,ir,l))
                    #y -= W1*B[l,l]'
                    mul!(y,W1,transpose(view(B,l,l)),-ONE,ONE)
                    #W2 = C[k,ir]*E[ir,l]
                    mul!(W1,view(C,k,ir),view(E,ir,l))
                    # y -= W1*D[l,l]'
                    mul!(y,W1,transpose(view(D,l,l)),-ONE,ONE)
                  end
                 if ll < pb
                    ic = i1:m
                    #WB[k,dll] = E[k,il1]*B[l,il1]'
                    mul!(view(WB,k,dll),view(E,k,il1),transpose(view(B,l,il1)))
                    #WD[k,dll] = E[k,il1]*D[l,il1]'
                    mul!(view(WD,k,dll),view(E,k,il1),transpose(view(D,l,il1)))
                    #y -= (A[k,ic]*WB[ic,dll]+C[k,ic]*WD[ic,dll])
                    mul!(y,view(A,k,ic),view(WB,ic,dll),-ONE,ONE)
                    mul!(y,view(C,k,ic),view(WD,ic,dll),-ONE,ONE)
                 end
                 gsylv2!(adjAC,adjBD,y,dk,dl,view(A,k,k),view(B,l,l),view(C,k,k),view(D,l,l),Xw)
                 i -= dk
             end
             j -= dl
         end
   elseif adjAC && !adjBD
      # """
      # The (K,L)th block of X is determined starting from the
      # upper-left corner column by column by

      # A(K,K)'*X(K,L)*B(L,L) + C(K,K)'*X(K,L)*D(L,L) = E(K,L) - R(K,L),

      # where
      #                       K-1
      #            R(K,L) = { SUM [A(J,K)'*X(J,L)] } * B(L,L) +
      #                       J=1
      #                        K              L-1
      #                       SUM A(J,K)' * { SUM [X(J,I)*B(I,L)] } +
      #                       J=1             I=1

      #                       K-1
      #                     { SUM [C(J,K)'*X(J,L)] } * D(L,L) +
      #                       J=1
      #                        K              L-1
      #                       SUM C(J,K)' * { SUM [X(J,I)*D(I,L)] }.
      #                       J=1             I=1
      # """
      j = 1
      for ll = 1:pb
          dl = bb[ll]
          dll = 1:dl
          il1 = 1:j-1
          j1 = j+dl-1
          l = j:j1
          i = 1
          for kk = 1:pa
              dk = ba[kk]
              dkk = 1:dk
              i1 = i+dk-1
              k = i:i1
              y = view(E,k,l)
              W1 = view(Xw,dkk,dll)
              if kk > 1
                 ir = 1:i-1
                 # W1 = A[ir,k]'*E[ir,l]
                 mul!(W1,transpose(view(A,ir,k)),view(E,ir,l))
                 #y -= W1*B[l,l]
                 mul!(y,W1,view(B,l,l),-ONE,ONE)
                 mul!(W1,transpose(view(C,ir,k)),view(E,ir,l))
                 #y -= W1*D[l,l]
                 mul!(y,W1,view(D,l,l),-ONE,ONE)
              end
              if ll > 1
                 ic = 1:i1
                 #WB[k,dll] = E[k,il1]*B[il1,l]
                 mul!(view(WB,k,dll),view(E,k,il1),view(B,il1,l))
                 #y -= A[ic,k]'*WB[ic,dll]
                 mul!(y,transpose(view(A,ic,k)),view(WB,ic,dll),-ONE,ONE)
                 #WD[k,dll] = E[k,il1]*D[il1,l]
                 mul!(view(WD,k,dll),view(E,k,il1),view(D,il1,l))
                 #y -= C[ic,k]'*WD[ic,dll]
                 mul!(y,transpose(view(C,ic,k)),view(WD,ic,dll),-ONE,ONE)
              end
              gsylv2!(adjAC,adjBD,y,dk,dl,view(A,k,k),view(B,l,l),view(C,k,k),view(D,l,l),Xw)
              i += dk
          end
          j += dl
      end
   elseif adjAC && adjBD
      # """
      # The (K,L)th block of X is determined starting from
      # upper-right corner column by column by

      #            A(K,K)'*X(K,L)*B(L,L)' + C(K,K)'*X(K,L)*D(L,L)' = E(K,L) - R(K,L)

      # where
      #                       K-1
      #            R(K,L) = { SUM [A(J,K)'*X(J,L)] } * B(L,L)' +
      #                       J=1
      #                        K               N
      #                       SUM A(J,K)' * { SUM [X(J,I)*B(L,I)'] }+
      #                       J=1            I=L+1

      #                       K-1
      #                     { SUM [C(J,K)'*X(J,L)] } * D(L,L)' +
      #                       J=1
      #                        K               N
      #                       SUM C(J,K)' * { SUM [X(J,I)*D(L,I)'] }.
      #                       J=1            I=L+1
      # """
      j = n
      for ll = pb:-1:1
          dl = bb[ll]
          dll = 1:dl
          il1 = j+1:n
          l = j-dl+1:j
          i = 1
          for kk = 1:pa
              dk = ba[kk]
              dkk = 1:dk
              i1 = i+dk-1
              k = i:i1
              y = view(E,k,l)
              W1 = view(Xw,dkk,dll)
              if kk > 1
                 ir = 1:i-1
                 #W1 = A[ir,k]'*E[ir,l]
                 mul!(W1,transpose(view(A,ir,k)),view(E,ir,l))
                 #y -= W1*B[l,l]'
                 mul!(y,W1,transpose(view(B,l,l)),-ONE,ONE)
                 #   W2 = C[ir,k]'*E[ir,l]
               #   y -= W2*D[l,l]'
                 mul!(W1,transpose(view(C,ir,k)),view(E,ir,l))
                 #y -= W1*D[l,l]'
                 mul!(y,W1,transpose(view(D,l,l)),-ONE,ONE)
               end
              if ll < pb
                 ic = 1:i1
                 #WB[k,dll] = E[k,il1]*B[l,il1]'
                 mul!(view(WB,k,dll),view(E,k,il1),transpose(view(B,l,il1)))
                 #WD[k,dll] = E[k,il1]*D[l,il1]'
                 mul!(view(WD,k,dll),view(E,k,il1),transpose(view(D,l,il1)))
                 #y -= (A[ic,k]'*WB[ic,dll] + C[ic,k]'*WD[ic,dll])
                 mul!(y,transpose(view(A,ic,k)),view(WB,ic,dll),-ONE,ONE)
                 mul!(y,transpose(view(C,ic,k)),view(WD,ic,dll),-ONE,ONE)
              end
              gsylv2!(adjAC,adjBD,y,dk,dl,view(A,k,k),view(B,l,l),view(C,k,k),view(D,l,l),Xw)
              i += dk
          end
          j -= dl
      end
   end
   return E
end
@inline function gsylv2!(adjAC::Bool,adjBD::Bool,E::StridedMatrix{T},na::Int,nb::Int,A::AbstractMatrix{T},B::AbstractMatrix{T},C::AbstractMatrix{T},D::AbstractMatrix{T},Xw::StridedMatrix{T}) where T <:BlasReal
   # speed and reduced allocation oriented implementation of a solver for 1x1 and 2x2 generalized Sylvester equations:
   #      A*X*B + C*X*D = E     if adjAC = false and adjBD = false -> R = kron(B',A)  + kron(D',C)
   #      A'*X*B + C'*X*D = E   if adjAC = true and adjBD = false  -> R = kron(B',A') + kron(D',C')
   #      A*X*B' + C*X*D' = E   if adjAC = false and adjBD = true  -> R = kron(B,A)   + kron(D,C)
   #      A'*X*B' + C'*X*D' = E if adjAC = true and adjBD = true   -> R = kron(B,A')  + kron(D,C')
   if na == 1 && nb == 1
      temp = A[1,1]*B[1,1] + C[1,1]*D[1,1]
      rmul!(E,inv(temp))
      any(!isfinite, E) &&  throw("ME:SingularException: `A-λC` and `D+λB` have common eigenvalues")
      return E
   end
   n = na*nb
   i1 = 1:n
   R = view(Xw,i1,i1)
   Y = reshape(E, n)
   if !adjAC && !adjBD
      if na == 1
         # R12 =
         # [ a11*b11 + c11*d11, a11*b21 + c11*d21]
         # [ a11*b12 + c11*d12, a11*b22 + c11*d22]
         # @inbounds R = [ A[1,1]*B[1,1]+C[1,1]*D[1,1]      A[1,1]*B[2,1]+C[1,1]*D[2,1];
         #                 A[1,1]*B[1,2]+C[1,1]*D[1,2]  A[1,1]*B[2,2]+C[1,1]*D[2,2]]
         @inbounds  R[1,1] = A[1,1]*B[1,1]+C[1,1]*D[1,1]
         @inbounds  R[1,2] = A[1,1]*B[2,1]+C[1,1]*D[2,1]
         @inbounds  R[2,1] = A[1,1]*B[1,2]+C[1,1]*D[1,2]
         @inbounds  R[2,2] = A[1,1]*B[2,2]+C[1,1]*D[2,2]
         # @inbounds  Y[1] = E[1,1]
         # @inbounds  Y[2] = E[1,2]
      else
         if nb == 1
            # R21 =
            # [ a11*b11 + c11*d11, a12*b11 + c12*d11]
            # [ a21*b11 + c21*d11, a22*b11 + c22*d11]
            # @inbounds R = [ A[1,1]*B[1,1]+C[1,1]*D[1,1]      A[1,2]*B[1,1]+C[1,2]*D[1,1];
            #                 A[2,1]*B[1,1]+C[2,1]*D[1,1]  A[2,2]*B[1,1]+C[2,2]*D[1,1] ]
            @inbounds  R[1,1] = A[1,1]*B[1,1]+C[1,1]*D[1,1]
            @inbounds  R[1,2] = A[1,2]*B[1,1]+C[1,2]*D[1,1]
            @inbounds  R[2,1] = A[2,1]*B[1,1]+C[2,1]*D[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+C[2,2]*D[1,1]
            # @inbounds  Y[1] = E[1,1]
            # @inbounds  Y[2] = E[2,1]
         else
            # R =
            # [ a11*b11 + c11*d11, a12*b11 + c12*d11, a11*b21 + c11*d21, a12*b21 + c12*d21]
            # [ a21*b11 + c21*d11, a22*b11 + c22*d11, a21*b21 + c21*d21, a22*b21 + c22*d21]
            # [ a11*b12 + c11*d12, a12*b12 + c12*d12, a11*b22 + c11*d22, a12*b22 + c12*d22]
            # [ a21*b12 + c21*d12, a22*b12 + c22*d12, a21*b22 + c21*d22, a22*b22 + c22*d22]
            # (iszero(C[2,1]) && iszero(D[2,1]) && iszero(C[1,2]) && iszero(D[1,2])) ?
            # (@inbounds R = [ A[1,1]*B[1,1]+C[1,1]*D[1,1]      A[1,2]*B[1,1]      A[1,1]*B[2,1]      A[1,2]*B[2,1];
            # A[2,1]*B[1,1]  A[2,2]*B[1,1]+C[2,2]*D[1,1]      A[2,1]*B[2,1]      A[2,2]*B[2,1];
            # A[1,1]*B[1,2]      A[1,2]*B[1,2]  A[1,1]*B[2,2]+C[1,1]*D[2,2]      A[1,2]*B[2,2];
            # A[2,1]*B[1,2]      A[2,2]*B[1,2]      A[2,1]*B[2,2]  A[2,2]*B[2,2]+C[2,2]*D[2,2]]) :
            # (@inbounds R = [ A[1,1]*B[1,1]+C[1,1]*D[1,1]      A[1,2]*B[1,1]+C[1,2]*D[1,1]      A[1,1]*B[2,1]+C[1,1]*D[2,1]      A[1,2]*B[2,1]+C[1,2]*D[2,1];
            # A[2,1]*B[1,1]+C[2,1]*D[1,1]  A[2,2]*B[1,1]+C[2,2]*D[1,1]      A[2,1]*B[2,1]+C[2,1]*D[2,1]      A[2,2]*B[2,1]+C[2,2]*D[2,1];
            # A[1,1]*B[1,2]+C[1,1]*D[1,2]      A[1,2]*B[1,2]+C[1,2]*D[1,2]  A[1,1]*B[2,2]+C[1,1]*D[2,2]      A[1,2]*B[2,2]+C[1,2]*D[2,2];
            # A[2,1]*B[1,2]+C[2,1]*D[1,2]      A[2,2]*B[1,2]+C[2,2]*D[1,2]      A[2,1]*B[2,2]+C[2,1]*D[2,2]  A[2,2]*B[2,2]+C[2,2]*D[2,2]])
            @inbounds  R[1,1] = A[1,1]*B[1,1]+C[1,1]*D[1,1]
            @inbounds  R[1,2] = A[1,2]*B[1,1]+C[1,2]*D[1,1]
            @inbounds  R[1,3] = A[1,1]*B[2,1]+C[1,1]*D[2,1]
            @inbounds  R[1,4] = A[1,2]*B[2,1]+C[1,2]*D[2,1]
            @inbounds  R[2,1] = A[2,1]*B[1,1]+C[2,1]*D[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+C[2,2]*D[1,1]
            @inbounds  R[2,3] = A[2,1]*B[2,1]+C[2,1]*D[2,1]
            @inbounds  R[2,4] = A[2,2]*B[2,1]+C[2,2]*D[2,1]
            @inbounds  R[3,1] = A[1,1]*B[1,2]+C[1,1]*D[1,2]
            @inbounds  R[3,2] = A[1,2]*B[1,2]+C[1,2]*D[1,2]
            @inbounds  R[3,3] = A[1,1]*B[2,2]+C[1,1]*D[2,2]
            @inbounds  R[3,4] = A[1,2]*B[2,2]+C[1,2]*D[2,2]
            @inbounds  R[4,1] = A[2,1]*B[1,2]+C[2,1]*D[1,2]
            @inbounds  R[4,2] = A[2,2]*B[1,2]+C[2,2]*D[1,2]
            @inbounds  R[4,3] = A[2,1]*B[2,2]+C[2,1]*D[2,2]
            @inbounds  R[4,4] = A[2,2]*B[2,2]+C[2,2]*D[2,2]
            # @inbounds  Y[1] = E[1,1]
            # @inbounds  Y[2] = E[2,1]
            # @inbounds  Y[3] = E[1,2]
            # @inbounds  Y[4] = E[2,2]
         end
      end
      #R = kron(transpose(B),A) + kron(transpose(D),C)
   elseif adjAC && !adjBD
      if na == 1
         # R12 =
         # [ a11*b11 + c11*d11, a11*b21 + c11*d21]
         # [ a11*b12 + c11*d12, a11*b22 + c11*d22]
         # @inbounds R = [ A[1,1]*B[1,1]+C[1,1]*D[1,1]      A[1,1]*B[2,1]+C[1,1]*D[2,1];
         #                 A[1,1]*B[1,2]+C[1,1]*D[1,2]  A[1,1]*B[2,2]+C[1,1]*D[2,2]]
         @inbounds  R[1,1] = A[1,1]*B[1,1]+C[1,1]*D[1,1]
         @inbounds  R[1,2] = A[1,1]*B[2,1]+C[1,1]*D[2,1]
         @inbounds  R[2,1] = A[1,1]*B[1,2]+C[1,1]*D[1,2]
         @inbounds  R[2,2] = A[1,1]*B[2,2]+C[1,1]*D[2,2]
         # @inbounds  Y[1] = E[1,1]
         # @inbounds  Y[2] = E[1,2]
      else
         if nb == 1
            # R21 =
            # [ a11*b11 + c11*d11, a21*b11 + c21*d11]
            # [ a12*b11 + c12*d11, a22*b11 + c22*d11]
            # @inbounds R = [ A[1,1]*B[1,1]+C[1,1]*D[1,1]      A[2,1]*B[1,1]+C[2,1]*D[1,1];
            #                 A[1,2]*B[1,1]+C[1,2]*D[1,1]  A[2,2]*B[1,1]+C[2,2]*D[1,1] ]
            @inbounds  R[1,1] = A[1,1]*B[1,1]+C[1,1]*D[1,1]
            @inbounds  R[1,2] = A[2,1]*B[1,1]+C[2,1]*D[1,1]
            @inbounds  R[2,1] = A[1,2]*B[1,1]+C[1,2]*D[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+C[2,2]*D[1,1]
            # @inbounds  Y[1] = E[1,1]
            # @inbounds  Y[2] = E[2,1]
         else
            # R =
            # [ a11*b11 + c11*d11, a21*b11 + c21*d11, a11*b21 + c11*d21, a21*b21 + c21*d21]
            # [ a12*b11 + c12*d11, a22*b11 + c22*d11, a12*b21 + c12*d21, a22*b21 + c22*d21]
            # [ a11*b12 + c11*d12, a21*b12 + c21*d12, a11*b22 + c11*d22, a21*b22 + c21*d22]
            # [ a12*b12 + c12*d12, a22*b12 + c22*d12, a12*b22 + c12*d22, a22*b22 + c22*d22]
            # (iszero(C[2,1]) && iszero(D[2,1]) && iszero(C[1,2]) && iszero(D[1,2])) ?
            # (@inbounds R = [ A[1,1]*B[1,1]+C[1,1]*D[1,1]      A[2,1]*B[1,1]      A[1,1]*B[2,1]      A[2,1]*B[2,1];
            # A[1,2]*B[1,1]  A[2,2]*B[1,1]+C[2,2]*D[1,1]      A[1,2]*B[2,1]      A[2,2]*B[2,1];
            # A[1,1]*B[1,2]      A[2,1]*B[1,2]  A[1,1]*B[2,2]+C[1,1]*D[2,2]      A[2,1]*B[2,2];
            # A[1,2]*B[1,2]      A[2,2]*B[1,2]      A[1,2]*B[2,2]  A[2,2]*B[2,2]+C[2,2]*D[2,2]]) :
            # (@inbounds R = [ A[1,1]*B[1,1]+C[1,1]*D[1,1]      A[2,1]*B[1,1]+C[2,1]*D[1,1]      A[1,1]*B[2,1]+C[1,1]*D[2,1]      A[2,1]*B[2,1]+C[2,1]*D[2,1];
            # A[1,2]*B[1,1]+C[1,2]*D[1,1]  A[2,2]*B[1,1]+C[2,2]*D[1,1]      A[1,2]*B[2,1]+C[1,2]*D[2,1]      A[2,2]*B[2,1]+C[2,2]*D[2,1];
            # A[1,1]*B[1,2]+C[1,1]*D[1,2]      A[2,1]*B[1,2]+C[2,1]*D[1,2]  A[1,1]*B[2,2]+C[1,1]*D[2,2]      A[2,1]*B[2,2]+C[2,1]*D[2,2];
            # A[1,2]*B[1,2]+C[1,2]*D[1,2]      A[2,2]*B[1,2]+C[2,2]*D[1,2]      A[1,2]*B[2,2]+C[1,2]*D[2,2]  A[2,2]*B[2,2]+C[2,2]*D[2,2]])
            @inbounds  R[1,1] = A[1,1]*B[1,1]+C[1,1]*D[1,1]
            @inbounds  R[1,2] = A[2,1]*B[1,1]+C[2,1]*D[1,1]
            @inbounds  R[1,3] = A[1,1]*B[2,1]+C[1,1]*D[2,1]
            @inbounds  R[1,4] = A[2,1]*B[2,1]+C[2,1]*D[2,1]
            @inbounds  R[2,1] = A[1,2]*B[1,1]+C[1,2]*D[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+C[2,2]*D[1,1]
            @inbounds  R[2,3] = A[1,2]*B[2,1]+C[1,2]*D[2,1]
            @inbounds  R[2,4] = A[2,2]*B[2,1]+C[2,2]*D[2,1]
            @inbounds  R[3,1] = A[1,1]*B[1,2]+C[1,1]*D[1,2]
            @inbounds  R[3,2] = A[2,1]*B[1,2]+C[2,1]*D[1,2]
            @inbounds  R[3,3] = A[1,1]*B[2,2]+C[1,1]*D[2,2]
            @inbounds  R[3,4] = A[2,1]*B[2,2]+C[2,1]*D[2,2]
            @inbounds  R[4,1] = A[1,2]*B[1,2]+C[1,2]*D[1,2]
            @inbounds  R[4,2] = A[2,2]*B[1,2]+C[2,2]*D[1,2]
            @inbounds  R[4,3] = A[1,2]*B[2,2]+C[1,2]*D[2,2]
            @inbounds  R[4,4] = A[2,2]*B[2,2]+C[2,2]*D[2,2]
            # @inbounds  Y[1] = E[1,1]
            # @inbounds  Y[2] = E[2,1]
            # @inbounds  Y[3] = E[1,2]
            # @inbounds  Y[4] = E[2,2]
         end
      end
      #R = kron(transpose(B),transpose(A)) + kron(transpose(D),transpose(C))
   elseif !adjAC && adjBD
      if na == 1
         # R12 =
         # [ a11*b11 + c11*d11, a11*b12 + c11*d12]
         # [ a11*b21 + c11*d21, a11*b22 + c11*d22]
         # @inbounds R = [ A[1,1]*B[1,1]+C[1,1]*D[1,1]      A[1,1]*B[1,2]+C[1,1]*D[1,2];
         #                 A[1,1]*B[2,1]+C[1,1]*D[2,1]  A[1,1]*B[2,2]+C[1,1]*D[2,2]]
         @inbounds  R[1,1] = A[1,1]*B[1,1]+C[1,1]*D[1,1]
         @inbounds  R[1,2] = A[1,1]*B[1,2]+C[1,1]*D[1,2]
         @inbounds  R[2,1] = A[1,1]*B[2,1]+C[1,1]*D[2,1]
         @inbounds  R[2,2] = A[1,1]*B[2,2]+C[1,1]*D[2,2]
         # @inbounds  Y[1] = E[1,1]
         # @inbounds  Y[2] = E[1,2]
      else
         if nb == 1
            # R21 =
            # [ a11*b11 + c11*d11, a12*b11 + c12*d11]
            # [ a21*b11 + c21*d11, a22*b11 + c22*d11]
            # @inbounds R = [ A[1,1]*B[1,1]+C[1,1]*D[1,1]      A[1,2]*B[1,1]+C[1,2]*D[1,1];
            #                 A[2,1]*B[1,1]+C[2,1]*D[1,1]  A[2,2]*B[1,1]+C[2,2]*D[1,1] ]
            @inbounds  R[1,1] = A[1,1]*B[1,1]+C[1,1]*D[1,1]
            @inbounds  R[1,2] = A[1,2]*B[1,1]+C[1,2]*D[1,1]
            @inbounds  R[2,1] = A[2,1]*B[1,1]+C[2,1]*D[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+C[2,2]*D[1,1]
            # @inbounds  Y[1] = E[1,1]
            # @inbounds  Y[2] = E[2,1]
         else
            # R =
            # [ a11*b11 + c11*d11, a12*b11 + c12*d11, a11*b12 + c11*d12, a12*b12 + c12*d12]
            # [ a21*b11 + c21*d11, a22*b11 + c22*d11, a21*b12 + c21*d12, a22*b12 + c22*d12]
            # [ a11*b21 + c11*d21, a12*b21 + c12*d21, a11*b22 + c11*d22, a12*b22 + c12*d22]
            # [ a21*b21 + c21*d21, a22*b21 + c22*d21, a21*b22 + c21*d22, a22*b22 + c22*d22]
            # (iszero(C[2,1]) && iszero(D[2,1]) && iszero(C[1,2]) && iszero(D[1,2])) ?
            # (@inbounds R = [ A[1,1]*B[1,1]+C[1,1]*D[1,1]      A[1,2]*B[1,1]      A[1,1]*B[1,2]      A[1,2]*B[1,2];
            # A[2,1]*B[1,1]  A[2,2]*B[1,1]+C[2,2]*D[1,1]      A[2,1]*B[1,2]      A[2,2]*B[1,2];
            # A[1,1]*B[2,1]      A[1,2]*B[2,1]  A[1,1]*B[2,2]+C[1,1]*D[2,2]      A[1,2]*B[2,2]+C[1,2]*D[2,2];
            # A[2,1]*B[2,1]      A[2,2]*B[2,1]      A[2,1]*B[2,2]  A[2,2]*B[2,2]+C[2,2]*D[2,2]]) :
            # (@inbounds R = [ A[1,1]*B[1,1]+C[1,1]*D[1,1]      A[1,2]*B[1,1]+C[1,2]*D[1,1]      A[1,1]*B[1,2]+C[1,1]*D[1,2]      A[1,2]*B[1,2]+C[1,2]*D[1,2];
            # A[2,1]*B[1,1]+C[2,1]*D[1,1]  A[2,2]*B[1,1]+C[2,2]*D[1,1]      A[2,1]*B[1,2]+C[2,1]*D[1,2]      A[2,2]*B[1,2]+C[2,2]*D[1,2];
            # A[1,1]*B[2,1]+C[1,1]*D[2,1]      A[1,2]*B[2,1]+C[1,2]*D[2,1]  A[1,1]*B[2,2]+C[1,1]*D[2,2]      A[1,2]*B[2,2]+C[1,2]*D[2,2];
            # A[2,1]*B[2,1]+C[2,1]*D[2,1]      A[2,2]*B[2,1]+C[2,2]*D[2,1]      A[2,1]*B[2,2]+C[2,1]*D[2,2]  A[2,2]*B[2,2]+C[2,2]*D[2,2]])
            @inbounds  R[1,1] = A[1,1]*B[1,1]+C[1,1]*D[1,1]
            @inbounds  R[1,2] = A[1,2]*B[1,1]+C[1,2]*D[1,1]
            @inbounds  R[1,3] = A[1,1]*B[1,2]+C[1,1]*D[1,2]
            @inbounds  R[1,4] = A[1,2]*B[1,2]+C[1,2]*D[1,2]
            @inbounds  R[2,1] = A[2,1]*B[1,1]+C[2,1]*D[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+C[2,2]*D[1,1]
            @inbounds  R[2,3] = A[2,1]*B[1,2]+C[2,1]*D[1,2]
            @inbounds  R[2,4] = A[2,2]*B[1,2]+C[2,2]*D[1,2]
            @inbounds  R[3,1] = A[1,1]*B[2,1]+C[1,1]*D[2,1]
            @inbounds  R[3,2] = A[1,2]*B[2,1]+C[1,2]*D[2,1]
            @inbounds  R[3,3] = A[1,1]*B[2,2]+C[1,1]*D[2,2]
            @inbounds  R[3,4] = A[1,2]*B[2,2]+C[1,2]*D[2,2]
            @inbounds  R[4,1] = A[2,1]*B[2,1]+C[2,1]*D[2,1]
            @inbounds  R[4,2] = A[2,2]*B[2,1]+C[2,2]*D[2,1]
            @inbounds  R[4,3] = A[2,1]*B[2,2]+C[2,1]*D[2,2]
            @inbounds  R[4,4] = A[2,2]*B[2,2]+C[2,2]*D[2,2]
            # @inbounds  Y[1] = E[1,1]
            # @inbounds  Y[2] = E[2,1]
            # @inbounds  Y[3] = E[1,2]
            # @inbounds  Y[4] = E[2,2]
         end
      end
      #R = kron(B,A) + kron(D,C)
   else
      if na == 1
         # R12 =
         # [ a11*b11 + c11*d11, a11*b12 + c11*d12]
         # [ a11*b21 + c11*d21, a11*b22 + c11*d22]
         # @inbounds R = [ A[1,1]*B[1,1]+C[1,1]*D[1,1]      A[1,1]*B[1,2]+C[1,1]*D[1,2];
         #                 A[1,1]*B[2,1]+C[1,1]*D[2,1]  A[1,1]*B[2,2]+C[1,1]*D[2,2]]
         @inbounds  R[1,1] = A[1,1]*B[1,1]+C[1,1]*D[1,1]
         @inbounds  R[1,2] = A[1,1]*B[1,2]+C[1,1]*D[1,2]
         @inbounds  R[2,1] = A[1,1]*B[2,1]+C[1,1]*D[2,1]
         @inbounds  R[2,2] = A[1,1]*B[2,2]+C[1,1]*D[2,2]
         # @inbounds  Y[1] = E[1,1]
         # @inbounds  Y[2] = E[1,2]
      else
         if nb == 1
            # R21 =
            # [ a11*b11 + c11*d11, a21*b11 + c21*d11]
            # [ a12*b11 + c12*d11, a22*b11 + c22*d11]
            # @inbounds R = [ A[1,1]*B[1,1]+C[1,1]*D[1,1]      A[2,1]*B[1,1]+C[2,1]*D[1,1];
            #                 A[1,2]*B[1,1]+C[1,2]*D[1,1]  A[2,2]*B[1,1]+C[2,2]*D[1,1] ]
            @inbounds  R[1,1] = A[1,1]*B[1,1]+C[1,1]*D[1,1]
            @inbounds  R[1,2] = A[2,1]*B[1,1]+C[2,1]*D[1,1]
            @inbounds  R[2,1] = A[1,2]*B[1,1]+C[1,2]*D[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+C[2,2]*D[1,1]
            # @inbounds  Y[1] = E[1,1]
            # @inbounds  Y[2] = E[2,1]
         else
            # R =
            # [ a11*b11 + c11*d11, a21*b11 + c21*d11, a11*b12 + c11*d12, a21*b12 + c21*d12]
            # [ a12*b11 + c12*d11, a22*b11 + c22*d11, a12*b12 + c12*d12, a22*b12 + c22*d12]
            # [ a11*b21 + c11*d21, a21*b21 + c21*d21, a11*b22 + c11*d22, a21*b22 + c21*d22]
            # [ a12*b21 + c12*d21, a22*b21 + c22*d21, a12*b22 + c12*d22, a22*b22 + c22*d22]
            # (iszero(C[2,1]) && iszero(D[2,1]) && iszero(C[1,2]) && iszero(D[1,2])) ?
            # (@inbounds R = [ A[1,1]*B[1,1]+C[1,1]*D[1,1]      A[2,1]*B[1,1]      A[1,1]*B[1,2]      A[2,1]*B[1,2];
            # A[1,2]*B[1,1]  A[2,2]*B[1,1]+C[2,2]*D[1,1]      A[1,2]*B[1,2]      A[2,2]*B[1,2];
            # A[1,1]*B[2,1]      A[2,1]*B[2,1]  A[1,1]*B[2,2]+C[1,1]*D[2,2]      A[2,1]*B[2,2];
            # A[1,2]*B[2,1]      A[2,2]*B[2,1]      A[1,2]*B[2,2]  A[2,2]*B[2,2]+C[2,2]*D[2,2]]) :
            # (@inbounds R = [ A[1,1]*B[1,1]+C[1,1]*D[1,1]      A[2,1]*B[1,1]+C[2,1]*D[1,1]      A[1,1]*B[1,2]+C[1,1]*D[1,2]      A[2,1]*B[1,2]+C[2,1]*D[1,2];
            # A[1,2]*B[1,1]+C[1,2]*D[1,1]  A[2,2]*B[1,1]+C[2,2]*D[1,1]      A[1,2]*B[1,2]+C[1,2]*D[1,2]      A[2,2]*B[1,2]+C[2,2]*D[1,2];
            # A[1,1]*B[2,1]+C[1,1]*D[2,1]      A[2,1]*B[2,1]+C[2,1]*D[2,1]  A[1,1]*B[2,2]+C[1,1]*D[2,2]      A[2,1]*B[2,2]+C[2,1]*D[2,2];
            # A[1,2]*B[2,1]+C[1,2]*D[2,1]      A[2,2]*B[2,1]+C[2,2]*D[2,1]      A[1,2]*B[2,2]+C[1,2]*D[2,2]  A[2,2]*B[2,2]+C[2,2]*D[2,2]])
            @inbounds  R[1,1] = A[1,1]*B[1,1]+C[1,1]*D[1,1]
            @inbounds  R[1,2] = A[2,1]*B[1,1]+C[2,1]*D[1,1]
            @inbounds  R[1,3] = A[1,1]*B[1,2]+C[1,1]*D[1,2]
            @inbounds  R[1,4] = A[2,1]*B[1,2]+C[2,1]*D[1,2]
            @inbounds  R[2,1] = A[1,2]*B[1,1]+C[1,2]*D[1,1]
            @inbounds  R[2,2] = A[2,2]*B[1,1]+C[2,2]*D[1,1]
            @inbounds  R[2,3] = A[1,2]*B[1,2]+C[1,2]*D[1,2]
            @inbounds  R[2,4] = A[2,2]*B[1,2]+C[2,2]*D[1,2]
            @inbounds  R[3,1] = A[1,1]*B[2,1]+C[1,1]*D[2,1]
            @inbounds  R[3,2] = A[2,1]*B[2,1]+C[2,1]*D[2,1]
            @inbounds  R[3,3] = A[1,1]*B[2,2]+C[1,1]*D[2,2]
            @inbounds  R[3,4] = A[2,1]*B[2,2]+C[2,1]*D[2,2]
            @inbounds  R[4,1] = A[1,2]*B[2,1]+C[1,2]*D[2,1]
            @inbounds  R[4,2] = A[2,2]*B[2,1]+C[2,2]*D[2,1]
            @inbounds  R[4,3] = A[1,2]*B[2,2]+C[1,2]*D[2,2]
            @inbounds  R[4,4] = A[2,2]*B[2,2]+C[2,2]*D[2,2]
            # @inbounds  Y[1] = E[1,1]
            # @inbounds  Y[2] = E[2,1]
            # @inbounds  Y[3] = E[1,2]
            # @inbounds  Y[4] = E[2,2]
         end
      end
      #R = kron(B,transpose(A)) + kron(D,transpose(C))
   end
   luslv!(R,Y) && throw("ME:SingularException: A has eigenvalue(s) α and B has eingenvalu(s) β such that αβ = -1")
   # E[:,:] = Y
   return E
end

function gsylvs!(A::AbstractMatrix{T1}, B::AbstractMatrix{T1}, C::AbstractMatrix{T1}, D::AbstractMatrix{T1}, E::AbstractMatrix{T1},
                 WB::AbstractVector{T1} = similar(A,size(A,1)), WD::AbstractVector{T1} = similar(A,size(A,1));
                 adjAC::Bool = false, adjBD::Bool = false, CASchur::Bool = false, DBSchur::Bool = false) where T1<:BlasComplex
   """
   An extension proposed in [1] of the Bartels-Stewart Schur form based approach [2] is employed.

   References:
   [1] K.-W. E. Chu. The solution of the matrix equation AXB – CXD = E and
       (YA – DZ, YC– BZ) = (E, F). Lin. Alg. Appl., 93:93-105, 1987.
   [2] R. H. Bartels and G. W. Stewart. Algorithm 432: Solution of the matrix equation AX+XB=C.
       Comm. ACM, 15:820–826, 1972.
   """
   m, n = size(E);
   [m; n; m; n] == LinearAlgebra.checksquare(A,B,C,D) ||
      throw(DimensionMismatch("A, B, C, D and E have incompatible dimensions"))
   m == length(WB) || throw(DimensionMismatch("WB must be an $m - dimensional vector"))
   m == length(WD) || throw(DimensionMismatch("WD must be an $m - dimensional vector"))

   WB = zeros(T1,m)
   WD = zeros(T1,m)
   ZERO = zero(T1)
   if !adjAC && !adjBD
      # """
      # The (K,L)th element of X is determined starting from
      # bottom-left corner column by column by

      #       A(K,K)*X(K,L)*B(L,L) +C(K,K)*X(K,L)*D(L,L) = E(K,L) - R(K,L)

      # where
      #                        M
      #            R(K,L) = { SUM [A(K,J)*X(J,L)] } * B(L,L) +
      #                      J=K+1
      #                        M             L-1
      #                       SUM { A(K,J) * SUM [X(J,I)*B(I,L)] } +
      #                       J=K            I=1

      #                       M
      #                    { SUM [C(K,J)*X(J,L)] } * D(L,L) +
      #                     J=K+1
      #                       M             L-1
      #                      SUM { C(K,J) * SUM [X(J,I)*D(I,L)] } +
      #                      J=K            I=1
      # """
      for l = 1:n
          il1 = 1:l-1
          for k = m:-1:1
              y = E[k,l]
              if k < m
                 ta = ZERO
                 tc = ZERO
                 for ir = k+1:m
                     ta += A[k,ir]*E[ir,l]
                     tc += C[k,ir]*E[ir,l]
                 end
                 y -= (ta*B[l,l]+tc*D[l,l])
              end
              if l > 1
                 ta = ZERO
                 tc = ZERO
                 for ir = il1
                     ta += E[k,ir]*B[ir,l]
                     tc += E[k,ir]*D[ir,l]
                 end
                 WB[k] = ta
                 WD[k] = tc
                 for ic = k:m
                     y -= (A[k,ic]*WB[ic]+C[k,ic]*WD[ic])
                 end
              end
              E[k,l] = y/(B[l,l]*A[k,k]+D[l,l]*C[k,k])
              isfinite(E[k,l]) || throw("ME:SingularException: A-λC and D+λB have common or close eigenvalues")
          end
      end
   elseif !adjAC && adjBD
         # """
         #  The (K,L)th element of X is determined starting from
         #  bottom-right corner column by column by

         #       A(K,K)*X(K,L)*B(L,L)' + C(K,K)*X(K,L)*D(L,L)' = E(K,L) - R(K,L)

         #  where
         #                        M
         #            R(K,L) = { SUM [A(K,J)*X(J,L)] } * B(L,L)' +
         #                      J=K+1
         #                        M              N
         #                       SUM { A(K,J) * SUM [X(J,I)*B(L,I)'] } +
         #                       J=K           I=L+1

         #                       M
         #                    { SUM [C(K,J)*X(J,L)] } * D(L,L)' +
         #                     J=K+1
         #                       M              N
         #                      SUM { C(K,J) * SUM [X(J,I)*D(L,I)'] }.
         #                      J=K           I=L+1
         # """
         for l = n:-1:1
             il1 = l+1:n
             for k = m:-1:1
                 y = E[k,l]
                 if k < m
                    ta = ZERO
                    tc = ZERO
                    for ir = k+1:m
                        ta += A[k,ir]*E[ir,l]
                        tc += C[k,ir]*E[ir,l]
                    end
                    y -= (ta*B[l,l]'+tc*D[l,l]')
                 end
                 if l < n
                    ta = ZERO
                    tc = ZERO
                    for ir = il1
                        ta += E[k,ir]*B[l,ir]'
                        tc += E[k,ir]*D[l,ir]'
                    end
                    WB[k] = ta
                    WD[k] = tc
                    for ic = k:m
                        y -= (A[k,ic]*WB[ic]+C[k,ic]*WD[ic])
                    end
                 end
                 E[k,l] = y/(B[l,l]'*A[k,k]+D[l,l]'*C[k,k])
                 isfinite(E[k,l]) || throw("ME:SingularException: A-λC and D'+λB' have common or close eigenvalues")
             end
         end
   elseif adjAC && !adjBD
      # """
      # The (K,L)th element of X is determined starting from the
      # upper-left corner column by column by

      # A(K,K)'*X(K,L)*B(L,L) + C(K,K)'*X(K,L)*D(L,L) = E(K,L) - R(K,L),

      # where
      #                       K-1
      #            R(K,L) = { SUM [A(J,K)'*X(J,L)] } * B(L,L) +
      #                       J=1
      #                        K              L-1
      #                       SUM A(J,K)' * { SUM [X(J,I)*B(I,L)] } +
      #                       J=1             I=1

      #                       K-1
      #                     { SUM [C(J,K)'*X(J,L)] } * D(L,L) +
      #                       J=1
      #                        K              L-1
      #                       SUM C(J,K)' * { SUM [X(J,I)*D(I,L)] }.
      #                       J=1             I=1
      # """
      for l = 1:n
          il1 = 1:l-1
          for k = 1:m
              y = E[k,l]
              if k > 1
                 ta = ZERO
                 tc = ZERO
                 for ir = 1:k-1
                     ta += A[ir,k]'*E[ir,l]
                     tc += C[ir,k]'*E[ir,l]
                 end
                 y -= (ta*B[l,l]+tc*D[l,l])
              end
              if l > 1
                 ta = ZERO
                 tc = ZERO
                 for ir = il1
                     ta += E[k,ir]*B[ir,l]
                     tc += E[k,ir]*D[ir,l]
                 end
                 WB[k] = ta
                 WD[k] = tc
                 for ic = 1:m
                     y -= (A[ic,k]'*WB[ic]+C[ic,k]'*WD[ic])
                 end
              end
              E[k,l] = y/(B[l,l]*A[k,k]'+D[l,l]*C[k,k]')
              isfinite(E[k,l]) || throw("ME:SingularException: A'-λC' and D+λB have common or close eigenvalues")
          end
      end
   elseif adjAC && adjBD
      # """
      # The (K,L)th element of X is determined starting from
      # upper-rght corner column by column by

      #       A(K,K)'*X(K,L)*B(L,L)' + C(K,K)'*X(K,L)*D(L,L)' = E(K,L) - R(K,L)

      # where
      #                       K-1
      #            R(K,L) = { SUM [A(J,K)'*X(J,L)] } * B(L,L)' +
      #                       J=1
      #                        K               N
      #                       SUM A(J,K)' * { SUM [X(J,I)*B(L,I)'] }+
      #                       J=1            I=L+1

      #                       K-1
      #                     { SUM [C(J,K)'*X(J,L)] } * D(L,L)' +
      #                       J=1
      #                        K               N
      #                       SUM C(J,K)' * { SUM [X(J,I)*D(L,I)'] }.
      #                       J=1            I=L+1
      # """
      for l = n:-1:1
          il1 = l+1:n
          for k = 1:m
              y = E[k,l]
              if k > 1
                 ta = ZERO
                 tc = ZERO
                 for ir = 1:k-1
                     ta += A[ir,k]'*E[ir,l]
                     tc += C[ir,k]'*E[ir,l]
                 end
                 y -= (ta*B[l,l]'+tc*D[l,l]')
               end
               if l < n
                 ic = 1:m
                 ta = ZERO
                 tc = ZERO
                 for ir = il1
                     ta += E[k,ir]*B[l,ir]'
                     tc += E[k,ir]*D[l,ir]'
                 end
                 WB[k] = ta
                 WD[k] = tc
                 for ic = 1:m
                     y -= (A[ic,k]'*WB[ic]+C[ic,k]'*WD[ic])
                 end
               end
              E[k,l] = y/(B[l,l]'*A[k,k]'+D[l,l]'*C[k,k]')
              isfinite(E[k,l]) || throw("ME:SingularException: A'-λC' and D'+λB' have common or close eigenvalues")
          end
      end
   end
   return E
end
"""
    (X,Y) = sylvsyss!(A,B,C,D,E,F)

Solve the Sylvester system of matrix equations

                AX + YB = C
                DX + YE = F,

where `(A,D)`, `(B,E)` are pairs of square matrices of the same size in generalized Schur forms.
The pencils `A-λD` and `-B+λE` must be regular and must not have common eigenvalues. The computed
solution `(X,Y)` is contained in `(C,F)`.

_Note:_ This is an enhanced interface to the `LAPACK.tgsyl!` function to also cover the case when
`A`, `B`, `D` and `E` are real matrices and `C` and `F` are complex matrices.
"""
function sylvsyss!(A::T1, B::T1, C::T1, D::T1, E::T1, F::T1) where {T<:BlasFloat,T1<:Matrix{T}}
   """
   This is a wrapper to the LAPACK.tgsyl! function with `trans = 'N'`.
   """
   C, F, scale =  tgsyl!('N',A,B,C,D,E,F)
   return rmul!(C,inv(scale)), rmul!(F,inv(-scale))
end
"""
    (X,Y) = dsylvsyss!(A,B,C,D,E,F)

Solve the dual Sylvester system of matrix equations

    A'X + D'Y = C
    XB' + YE' = F,

where `(A,D)`, `(B,E)` are pairs of square matrices of the same size in generalized Schur forms.
The pencils `A-λD` and `-B+λE` must be regular and must not have common eigenvalues. The computed
solution `(X,Y)` is contained in `(C,F)`.
"""
function dsylvsyss!(A::T1, B::T1, C::T1, D::T1, E::T1, F::T1) where {T<:BlasFloat,T1<:Matrix{T}}
   """
   This is an interface to the LAPACK.tgsyl! function with `trans = 'T' or `trans = 'C'`.
   """
   # MF = -F
   # E, F, scale =  tgsyl!(T <: Complex ? 'C' : 'T', A, B, C, D, E, MF)
   # F = MF
   # return rmul!(C[:,:],inv(scale)), rmul!(F[:,:],inv(scale))
   F = -F
   C, F, scale =  tgsyl!(T <: Complex ? 'C' : 'T', A, B, C, D, E, F)
   return rmul!(C[:,:],inv(scale)), rmul!(F[:,:],inv(scale))
end


module KuboOscillatorProblem

    using GeometricIntegrators.Equations

    export kubo_oscillator_sde_1, kubo_oscillator_psde_1, kubo_oscillator_spsde_1
    export kubo_oscillator_sde_2, kubo_oscillator_psde_2, kubo_oscillator_spsde_2
    export kubo_oscillator_sde_3, kubo_oscillator_psde_3, kubo_oscillator_spsde_3

    q_init_A=[0.5, 0.0]
    q_init_B=[0.5 0.0 -0.5; 0.0 0.5 0.0]

    const noise_intensity = 0.1

    const Δt = 0.01
    const nt = 10

    function kubo_oscillator_sde_v(t, q, v_out)
        v_out[1]=  q[2]
        v_out[2]= -q[1]
    end

    function kubo_oscillator_sde_B(t, q, B_out::AbstractVector, ν=noise_intensity)
        B_out[1] = +ν*q[2]
        B_out[2] = -ν*q[1]
    end

    function kubo_oscillator_sde_B(t, q, B_out::AbstractMatrix, col=1, ν=noise_intensity)
        B_out[1,col] = +ν*q[2]
        B_out[2,col] = -ν*q[1]
    end

    function kubo_oscillator_sde_1(q₀=q_init_A)
        # q_init_A - single deterministic initial condition
        # Generating 3 sample paths
        # 1-dimensional noise
        SDE(1, 3, kubo_oscillator_sde_v, kubo_oscillator_sde_B, q₀)
    end

    function kubo_oscillator_sde_2(q₀=q_init_B)
        # q_init_B - interpreted as three random initial conditions
        # The 3 columns correspond to 3 sample paths
        # 1-dimensional noise
        SDE(1, 1, kubo_oscillator_sde_v, kubo_oscillator_sde_B, q₀)
    end

    function kubo_oscillator_sde_3(q₀=q_init_A)
        # q_init_A - interpreted as one random initial conditions with one sample path
        # 1-dimensional noise
        SDE(1, 1, kubo_oscillator_sde_v, kubo_oscillator_sde_B, q₀)
    end

    # PSDE

    q_init_C=[0.5]
    p_init_C=[0.0]

    q_init_D=[0.5 0.0 -0.5]
    p_init_D=[0.0 0.5 0.0]

    function kubo_oscillator_psde_v(t, q, p, v_out)
        v_out[1] =  p[1]
    end

    function kubo_oscillator_psde_f(t, q, p, f_out)
        f_out[1] = -q[1]
    end

    function kubo_oscillator_psde_B(t, q, p, B_out, ν=noise_intensity)
        B_out[1,1] = +ν*p[1]
    end

    function kubo_oscillator_psde_G(t, q, p, G_out, ν=noise_intensity)
        G_out[1,1] = -ν*q[1]
    end

    function kubo_oscillator_psde_1(q₀=q_init_C, p₀=p_init_C)
        # q_init_C - single deterministic initial condition
        # Generating 3 sample paths
        # 1-dimensional noise
        PSDE(1, 3, kubo_oscillator_psde_v, kubo_oscillator_psde_f, kubo_oscillator_psde_B, kubo_oscillator_psde_G, q₀, p₀)
    end

    function kubo_oscillator_psde_2(q₀=q_init_D, p₀=p_init_D)
        # q_init_D - interpreted as a single random initial condition
        # The 3 columns correspond to 3 sample paths
        # 1-dimensional noise
        PSDE(1, 1, kubo_oscillator_psde_v, kubo_oscillator_psde_f, kubo_oscillator_psde_B, kubo_oscillator_psde_G, q₀, p₀)
    end

    function kubo_oscillator_psde_3(q₀=q_init_C, p₀=p_init_C)
        # q_init_C - interpreted as a single random initial condition with one sample path
        # 1-dimensional noise
        PSDE(1, 1, kubo_oscillator_psde_v, kubo_oscillator_psde_f, kubo_oscillator_psde_B, kubo_oscillator_psde_G, q₀, p₀)
    end

    # SPSDE

    function kubo_oscillator_spsde_v(t, q, p, v_out)
        v_out[1] =  p[1]
    end

    function kubo_oscillator_spsde_f1(t, q, p, f_out)
        f_out[1] = -q[1]
    end

    function kubo_oscillator_spsde_f2(t, q, p, f_out)
        f_out[1] = 0
    end

    function kubo_oscillator_spsde_B(t, q, p, B_out, ν=noise_intensity)
        B_out[1,1] = +ν*p[1]
    end

    function kubo_oscillator_spsde_G1(t, q, p, G_out, ν=noise_intensity)
        G_out[1,1] = -ν*q[1]
    end

    function kubo_oscillator_spsde_G2(t, q, p, G_out, ν=noise_intensity)
        G_out[1,1] = 0
    end

    function kubo_oscillator_spsde_1(q₀=q_init_C, p₀=p_init_C)
        # q_init_C - single deterministic initial condition
        # Generating 3 sample paths
        # 1-dimensional noise
        SPSDE(1, 3, kubo_oscillator_spsde_v, kubo_oscillator_spsde_f1, kubo_oscillator_spsde_f2, kubo_oscillator_spsde_B, kubo_oscillator_spsde_G1, kubo_oscillator_spsde_G2, q₀, p₀)
    end

    function kubo_oscillator_spsde_2(q₀=q_init_D, p₀=p_init_D)
        # q_init_D - interpreted as a single random initial condition
        # The 3 columns correspond to 3 sample paths
        # 1-dimensional noise
        SPSDE(1, 1, kubo_oscillator_spsde_v, kubo_oscillator_spsde_f1, kubo_oscillator_spsde_f2, kubo_oscillator_spsde_B, kubo_oscillator_spsde_G1, kubo_oscillator_spsde_G2, q₀, p₀)
    end

    function kubo_oscillator_spsde_3(q₀=q_init_C, p₀=p_init_C)
        # q_init_C - interpreted as a single random initial condition with one sample path
        # 1-dimensional noise
        SPSDE(1, 1, kubo_oscillator_spsde_v, kubo_oscillator_spsde_f1, kubo_oscillator_spsde_f2, kubo_oscillator_spsde_B, kubo_oscillator_spsde_G1, kubo_oscillator_spsde_G2, q₀, p₀)
    end

end


function ldlt!(a::Matrix{Float64}, d = Vector{Float64}; small=1.0e-10)
  n = size(a, 1)
  for k in 1:n-1
    d[1] = a[1,1]
    if abs(a[k,k]) > small
      for i in k+1:n
        x = a[i,k] / a[k,k]
        for j = k+1:n
          a[i,j] -= a[k,j] * x
        end
        d[i] = a[i,i]
      end
    else
      println("Zero pivot found in row $k")
    end
  end
end

function ldlt(a::Matrix{Float64}, d = Vector{Float64}; small=1.0e-10)
  at = deepcopy(a)
  dt = deepcopy(d)
  n = size(at, 1)
  for k in 1:n-1
    dt[1] = at[1,1]
    if abs(at[k,k]) > small
      for i in k+1:n
        x = at[i,k] / at[k,k]
        for j = k+1:n
          at[i,j] -= at[k,j] * x
        end
        dt[i] = at[i,i]
      end
    else
      println("Zero pivot found in row $k")
    end
  end
  (at, dt)
end

export
  ldlt!,
  ldlt


export proxL21!, normL21

"""
    proxL21!(x::Vector{T},λ::Float64; sparseTrafo::Trafo=nothing, slices::Int64=1, kargs...)

group-soft-thresholding for l1/l2-regularization.

# Arguments:
* `x::Array{T}`                 - Vector to apply proximal map to
* `λ::Float64`                  - regularization paramter
* `sparseTrafo::Trafo=nothing`  - sparsifying transform to apply
* `slices::Int64=1`             - number of elements per group
"""
function proxL21!(x::Vector{T},λ::Float64; sparseTrafo::Trafo=nothing, slices::Int64=1, kargs...) where T
  if sparseTrafo != nothing
    z = sparseTrafo*x
  else
    z = x
  end
  if λ != 0
    proxL21!(z, λ, slices)
  end
  if sparseTrafo != nothing
    x[:] = adjoint(sparseTrafo)*z
  else
    x[:] = z
  end
  return x
end

function proxL21!(x::Vector{T}, λ::Float64, slices::Int64) where T
  sliceLength = div(length(x),slices)
  groupNorm = [norm(x[i:sliceLength:end]) for i=1:sliceLength]
  x[:] = [ x[i]*max( (groupNorm[mod1(i,sliceLength)]-λ)/groupNorm[mod1(i,sliceLength)],0 ) for i=1:length(x)]
end

"""
    normL21(x::Vector{T}, λ::Float64; sparseTrafo::Trafo=nothing, slices::Int64=1, kargs...) where T

return the value of the L21-regularization term.
Arguments are the same as in `proxL21!`
"""
function normL21(x::Vector{T}, λ::Float64; sparseTrafo::Trafo=nothing, slices::Int64=1, kargs...) where T
  if sparseTrafo != nothing
    z = sparseTrafo*x
  else
    z = x
  end
  sliceLength = div(length(z),slices)
  groupNorm = [norm(z[i:sliceLength:end]) for i=1:sliceLength]
  return λ*norm(groupNorm,1)
end


# Let d(n) be defined as the sum of proper divisors of n (numbers less than n
# which divide evenly into n).  If d(a) = b and d(b) = a, where a ≠ b, then a
# and b are an amicable pair and each of a and b are called amicable numbers.
#
# For example, the proper divisors of 220 are 1, 2, 4, 5, 10, 11, 20, 22, 44,
# 55 and 110; therefore d(220) = 284. The proper divisors of 284 are 1, 2, 4,
# 71 and 142; so d(284) = 220.
#
# Evaluate the sum of all the amicable numbers under 10000.

using ProjectEulerSolutions

# Actually build up an array of factors, testing for amicable numbers smaller
# than the current index.  Building up is done with a double loop, adding like
# below.
#
# 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
#    1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
#             2     2     2     2     2     2     2
#                   3        3        3        3
#                         4           4           4
#                               5              5
#                                     6
#                                           7
#                                                 8
function p021solution_incremental(n::Integer=10)::Integer
    amicable_numbers = Array{Integer, 1}()

    factor_sums = ones(Integer, n)
    for i = 2:n
        for j = 2*i:i:n
            factor_sums[j] += i
        end
        x = factor_sums[i]
        if x < i && i == factor_sums[x]
            push!(amicable_numbers, x)
            push!(amicable_numbers, i)
        end
    end
    return sum(amicable_numbers)
end

# Repeatedly uses factors function and cache values so you don't
# recalculate.  Store in a set so you get unique values.
function p021solution_factors(n::Integer=10)::Integer
    factor_sums = zeros(Integer, 5*n) # Initialize to some large size

    amicable_numbers = Set{Integer}()

    for i in 2:n
        s1 = factor_sums[i]
        if s1 == 0
            s1 = sum(factors(i))
            factor_sums[i] = s1
        end
        s2 = factor_sums[s1]
        if s2 == 0
            s2 = sum(factors(s1))
            factor_sums[s1] = s2
        end
        if s2 == i && s1 != s2
            push!(amicable_numbers, s1)
            push!(amicable_numbers, s2)
        end
    end
    return sum(amicable_numbers)
end

p021 = Problems.Problem(Dict("Incremental" => p021solution_incremental,
                             "Factors" => p021solution_factors))

Problems.benchmark(p021, 10_000)


#=
# The BUGS Book
# Example 2.1.2, page 17:
# For
#   Y ~ Binomial(0.5, 8)
# we want to know Pr(Y <= 2)
#

  See ~/jags/bugs_book_2_1_2.jags
       Mean     SD Naive SE Time-series SE
  P2 0.1442 0.3513 0.001434       0.001434
  Y  4.0025 1.4177 0.005788       0.005839

  ~/webppl/bugs_book_2_1_2.wppl

=#

using Turing, StatsPlots, DataFrames
include("jl_utils.jl")

@model function bugs_book_2_1_2()

    y ~ Binomial(8,0.5)
    p2 ~ y <= 2 ? flip(1.0) : flip(0.0);

    return y, p2

end

model = bugs_book_2_1_2()

num_chains = 4
# chains = sample(model, Prior(), MCMCThreads(), 10_000, num_chains)

# chains = sample(model, MH(), MCMCThreads(), 40_000, num_chains)

# chains = sample(model, PG(15), MCMCThreads(), 10_000, num_chains)

chains = sample(model, SMC(1000), MCMCThreads(), 10_000, num_chains)

# chains = sample(model, IS(), MCMCThreads(), 10_000, num_chains)

display(chains)
# display(plot(chains))

gen = generated_quantities(model, chains)
show_var_dist_pct(gen, 120)


@doc raw"""
The Cauchy or Paul wavelet.

In contrast to the `MorletWavelet`, the Cauchy or Paul wavelet is a proper wavelet with a
similar good localization in time and scale.

There are different definitions of the Cauchy wavelet around. Here one is implemented where
the center frequency is always 1 irrespective of the value of α:
```math
 g(t) = h(t) = \left(1-i\,\frac{2\pi\,t}{\alpha}\right)^{-(1+\alpha)}\,,
```
and its reproducing kernel
```math
 P_{g,h}(b, a) = \Gamma(2\alpha+1)\,a^{\alpha+1}\,\left(1+a-\frac{i\,b}{a}\right)^{-(2\alpha+1)}\,.
```
"""
struct CauchyWavelet <: GenericContinuousWavelet
    α::Float64;
    norm::Float64;
    ϵ::Float64

    @doc raw"""
        CauchyWavelet(α::Real; ϵ::Real=1e-2)

    Constructs a new Cauchy wavelet, whith the given α specifying the time-frequency resolution.
    The optional keyword arguent ϵ specifies the cutoff at which the kernel evaluation gets
    truncated. It is defined as the fraction of total power loss of the mother wavelet. Smaller
    values of ϵ will increase the precision of the wavelet transform on the cost of longer kernels
    leading to slower convolutions.
    """
    function CauchyWavelet(α::Real; ϵ::Real=1e-2)
        norm = exp(-2*log(2π) - logabsgamma(2α+1)[1]/2 + logabsgamma(α+1)[1]
          + (2α+2)*log(2)/2 + log(α));
        new(α, norm, ϵ);
    end
end

function eval_analysis(wav::CauchyWavelet, t::Float64)
    (1 - 2im*π*t/wav.α)^(-1-wav.α);
end

eval_synthesis(wav::CauchyWavelet, t::Float64) = wav.norm * eval_analysis(wav, t);

function eval_repkern(wav::CauchyWavelet, a::Float64, b::Float64)
    c = wav.α*log(a) + logabsgamma(2*wav.α-1)[1] - (1+2*wav.α)*log(2π);
    exp(c) * (1+a-2im*π*b/wav.α)^(-1-2*wav.α);
end

function cutoff_time(wav::CauchyWavelet)
    wav.α*sqrt(wav.ϵ^(-2/(wav.α+1))-1)/(2π);
end;

function cutoff_freq(wav::CauchyWavelet)
    1 + 1/( wav.α^2 * (wav.ϵ^(-2 / (wav.α + 1)) - 1) / ((2π)^2) );
end;


function dbeta(p::Real, a::Real, b::Real)
    any((a, b) .<= 0) ? (return NaN) : nothing
    !(0 <= p <= 1) ? (return 0.0) : nothing
    return gamma(a + b)/gamma(a)/gamma(b)*(1 - p)^(b - 1)*p^(a - 1)
end
function pbeta(p::Real, a::Real, b::Real)
    any((a, b) .<= 0) ? (return NaN) : nothing
    p <= 0  ? (return 0.0) : nothing
    p >= 1  ? (return 1.0) : nothing
    return beta_inc(a, b, p, 1 - p)[1]
end

gauss_legendre = function(low, high, order::Integer)
    pivots, weights = gauss(order)
    a, b = (high - low)/2, (high + low)/2
    scaled_pivots  = a .* pivots .+ b
    scaled_weights = a .* weights
    return(scaled_pivots, scaled_weights)
end

# precompute
gl_25_pivots, gl_25_weights = gauss_legendre(-1, 1, 25)
gauss_legendre_25 = function(low, high)
    pivots, weights = gl_25_pivots, gl_25_weights
    a, b = (high - low)/2, (high + low)/2
    scaled_pivots  = a .* pivots .+ b
    scaled_weights = a .* weights
    return(scaled_pivots, scaled_weights)
end

function one_stage_sample_size(p0::Real, α::Real, p1::Real, β::Real)
    z_1_α   = Distributions.quantile(Distributions.Normal(), 1 - α)
    z_1_β   = Distributions.quantile(Distributions.Normal(), 1 - β)
    napprox = p1*(1 - p1)*( (z_1_α + z_1_β) / (p1 - p0) )^2
    return Int(ceil(napprox))
end


#=
Problem 101
=#

function u_gen(k::Array, n)
    sum = 0
    for i = length(k):-1:1
        sum = k[i] + sum * n
    end
    return sum
end

N = 11

# first 11 terms
u_n = Vector{Float64}(N)
for i=1:N
    u_n[i] = u_gen([1 -1 1 -1 1 -1 1 -1 1 -1 1], i)
end

# Matrix of coefficients
A = Array{Float64}(N,N)
for i=1:N, j=1:N
    A[i,j] = i^(j-1)
end

sum = 0
for i=1:N-1
    coeff = Array{Int64}(round(inv(A[1:i, 1:i]) * u_n[1:i]))
    FIT = u_gen(coeff, i+1)
    @show coeff
    @show FIT
    sum += FIT
end

println("Sum of FITs is $(sum)")

# Alternatively u_n = A*[1 -1 1 -1 1 -1 1 -1 1 -1 1]'


#=

  Euler #16 in Julia.

  Problem 16
  """
  2^15 = 32768 and the sum of its digits is 3 + 2 + 7 + 6 + 8 = 26.

  What is the sum of the digits of the number 2^1000?
  """

  This Julia program was created by Hakan Kjellerstrand, hakank@gmail.com
  See also my Julia page: http://www.hakank.org/julia/

=#

include("Euler.jl")

# 0.00027433s
function euler16a()
    # return split(string(BigInt(2)^1000),"").|>(i->parse(Int,i))|>sum
    return digits(BigInt(2)^1000)|>sum
end

run_euler(euler16a);


# Euler's Totient function, φ(n) [sometimes called the phi function], is used
# to determine the number of numbers less than n which are relatively prime to
# n. For example, as 1, 2, 4, 5, 7, and 8, are all less than nine and relatively
# prime to nine, φ(9)=6.
#
# n   Relatively Prime  φ(n)   n/φ(n)
# --------------------------------------
# 2   1                 1      2
# 3   1,2               2      1.5
# 4   1,3               2      2
# 5   1,2,3,4           4      1.25
# 6   1,5               2      3
# 7   1,2,3,4,5,6       6      1.1666...
# 8   1,3,5,7           4      2
# 9   1,2,4,5,7,8       6      1.5
# 10  1,3,7,9           4      2.5
#
# It can be seen that n=6 produces a maximum n / φ(n) for n ≤ 10.
#
# Find the value of n ≤ 1,000,000 for which n / φ(n) is a maximum.

using ProjectEulerSolutions

# Brute force solution, considering every value less than n.
function p069solution_bruteforce(n::Integer=1_000)::Integer

    max_val = 0.0
    max_index = 0
    for i in 2:n
        test_val = i / totient(i)
        if max_val < test_val
            max_val = test_val
            max_index = i
        end
    end

    return max_index
end

# Only consider values that are likely candidates, which are primodial numbers.
# Primodial numbers are the product of consecutive primes starting at 2, so
# 2, 6, 30, 210, etc.  We only consider primordial numbers up to that who's
# product more than n since they have maximal n / φ(n) values.
function p069solution_factors(n::Integer=1_000)::Integer

    primes = sieve_eratosthenes(30)
    index = 2
    p = primes[1]
    while p * primes[index] < n
        p *= primes[index]
        index += 1
    end
    return p
end

p069 = Problems.Problem(Dict("Brute force" => p069solution_bruteforce,
                             "Factors" => p069solution_factors))

Problems.benchmark(p069, 1_000_000)


@doc raw"""
    Otsu <: AbstractImageBinarizationAlgorithm
    Otsu()

    binarize([T,] img, f::Otsu)
    binarize!([out,] img, f::Otsu)

Under the assumption that the image histogram is bimodal the binarization
threshold is set so that the resultant between-class variance is maximal.

# Output

Return the binarized image as an `Array{Gray{T}}` of size `size(img)`. If
`T` is not specified, it is inferred from `out` and `img`.

# Details

Let ``f_i`` ``(i=1 \ldots I)`` denote the number of observations in the
``i``th bin of the image histogram. Then the probability that an observation
belongs to the ``i``th bin is given by  ``p_i = \frac{f_i}{N}`` (``i = 1,
\ldots, I``), where ``N = \sum_{i=1}^{I}f_i``.

The choice of a threshold ``T`` partitions the data into two categories, ``C_0``
and ``C_1``. Let
```math
P_0(T) = \sum_{i = 1}^T p_i \quad \text{and} \quad P_1(T) = \sum_{i = T+1}^I p_i
```
denote the cumulative probabilities,
```math
\mu_0(T) = \sum_{i = 1}^T i \frac{p_i}{P_0(T)} \quad \text{and} \quad \mu_1(T) = \sum_{i = T+1}^I i \frac{p_i}{P_1(T)}
```
denote the means, and
```math
\sigma_0^2(T) = \sum_{i = 1}^T (i-\mu_0(T))^2 \frac{p_i}{P_0(T)} \quad \text{and} \quad \sigma_1^2(T) = \sum_{i = T+1}^I (i-\mu_1(T))^2 \frac{p_i}{P_1(T)}
```
denote the variances of categories ``C_0`` and ``C_1``, respectively.
Furthermore, let
```math
\mu = P_0(T)\mu_0(T) + P_1(T)\mu_1(T),
```
represent the overall mean,
```math
\sigma_b^2(T) = P_0(T)(\mu_0(T) - \mu)^2 + P_1(T)(\mu_1(T) - \mu)^2,
```
the between-category variance, and
```math
\sigma_w^2(T) = P_0(T) \sigma_0^2(T) +  P_1(T)\sigma_1^2(T)
```
the within-category variance, respectively.

Finding the discrete value ``T`` which maximises the function ``\sigma_b^2(T)``
produces the sought-after threshold value (i.e. the bin which determines the
threshold). As it turns out, that threshold value is equal to the threshold
decided by minimizing the within-category variances criterion ``\sigma_w^2(T)``.
Furthermore, that threshold is also the same as the threshold calculated by
maximizing the ratio of between-category variance to within-category variance.

# Arguments

The function argument is described in more detail below.

##  `img::AbstractArray`

The image that needs to be binarized.  The image is automatically converted
to `Gray` in order to construct the requisite graylevel histogram.

# Example

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

```julia
using TestImages, ImageBinarization

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

# Reference

1. Nobuyuki Otsu (1979). “A threshold selection method from gray-level histograms”. *IEEE Trans. Sys., Man., Cyber.* 9 (1): 62–66. [doi:10.1109/TSMC.1979.4310076](http://dx.doi.org/doi:10.1109/TSMC.1979.4310076)
"""
struct Otsu <: AbstractImageBinarizationAlgorithm end

function (f::Otsu)(out::GenericGrayImage, img::GenericGrayImage)
    edges, counts = build_histogram(img,  256)
    t = find_threshold(HistogramThresholding.Otsu(), counts[1:end], edges)
    @simd for i in CartesianIndices(img)
        out[i] = img[i] < t ? 0 : 1
    end
    out
end

(f::Otsu)(out::GenericGrayImage, img::AbstractArray{<:Color3}) =
    f(out, of_eltype(Gray, img))