using TimeSchemes
using Test

##
M = 1
B = 1
K = 1
v0 = [1.]
v1 = [0.]
t = convert(Array{Float64},range(0, stop=1, length=10001))'
F = zeros(length(t))'
v, cV = TimeSchemes.newmark(M, B, K, F, v0, v1, t)

sol = 1 / 3 * exp.(- t / 2) .* (sqrt(3) * sin.(sqrt(3) / 2 * t) + 3  * cos.(sqrt(3) / 2 * t))
sold = - 1 / 6 * exp.(- t / 2) .* (sqrt.(3) * sin.(sqrt(3) / 2 * t) + 3  * cos.(sqrt(3) / 2 * t)) +
    1 / 3 * exp.(- t / 2) .* (3 / 2 * cos.(sqrt(3) / 2 * t) - 3 * sqrt(3) / 2 * sin.(sqrt(3) / 2 * t))
@test (maximum(abs.(v[1, :]' - sol)) < 1e-9) && (maximum(abs.(v[2, :]' - sold)) < 1e-9)
println("All tests passed.")


"""
Produce an elliptic torus with circular tube cross
section
"""
module MajorEllipticTorus

# minimum distance between a point and ellipse
function min_distance(a, b, coordinates)
    x = abs(coordinates[1])
    y = abs(coordinates[2])
    z = coordinates[3]
    function aux(phi, accum)
        new_phi = atan(((a^2 - b^2)*sin(phi) + y*b)/(x*a))
        if abs(phi - new_phi) < 1e-4
            return new_phi
        else
            return aux(new_phi, accum+1)
        end
    end
    phi = aux(0, 0)
    return sqrt(z^2 + (x - a * cos(phi))^2 + (y - b*sin(phi))^2)
end

# check whether a point is inside the torus

function get_fn(argd::Dict)
    a = float(argd["major"]["a"])
    b = float(argd["major"]["b"])
    r = float(argd["minor"]["radius"])
    center = [float(x) for x in argd["center"]]
    force_constant = float(argd["k"])

    function fn_compute(raw_coordinates)
        coordinates = raw_coordinates - center
        if norm(coordinates[1:2]) < 1e-6
            d = sqrt(min(a,b)^2 + coordinates[3]^2)
        else
            d = min_distance(a, b, coordinates)
        end
        if d < r
            return 0
        else
            return force_constant * d
        end
    end
    return fn_compute
end

export get_fn

end


module FractionalCalculus

using QuadGK
using SpecialFunctions: gamma
using LinearAlgebra, InvertedIndices, SpecialMatrices
using SymbolicUtils, SymbolicUtils.Rewriters
using Symbolics

include("./Derivative/Derivative.jl")
include("./Integral/Integral.jl")

export FracDiffAlg, FracIntAlg
export fracdiff, fracint

# Export symbolics computing APIs
export semidiff, semiint
export SEMIDIFFRULES, SEMIINTRULES
export Incomplete_beta, AuxiliaryFresnelSin, AuxiliaryFresnelCos, Struve, MStruve, Legendre, SinIntegral, HyperSinIntegral, Hypergeometric1F1

# Export fractional derivative releating API
export Caputo, GL, RLDiff, Hadamard

# Caputo sense fractional derivative
export CaputoDirect, CaputoPiecewise, CaputoDiethelm, CaputoHighPrecision, CaputoHighOrder

# Grünwald Letnikov sense fractional derivative
export GLDirect, GLMultiplicativeAdditive, GLLagrangeThreePointInterp, GLFiniteDifference, GLHighPrecision

# Riemann Liouville sense fractional derivative
export RLDiffApprox, RLDiffMatrix, RLLinearSplineInterp, RLG1, RLD

# Hadamard sense fractional derivative
export HadamardLRect, HadamardRRect, HadamardTrap

# Riesz sense fractional derivative
export RieszSymmetric, RieszOrtigueira

# Macros for convenient computing
export @fracdiff, @semifracdiff

#Export fractional integral relating API
export RLInt, HadamardInt

export RLDirect, RLPiecewise
export RLIntApprox, RLLinearInterp, RLIntMatrix
export RLIntSimpson, RLIntTrapezoidal, RLIntRectangular, RLIntCubicSplineInterp

export HadamardMat

# Macros for convenient computing
export @fracint, @semifracint

# Auxiliary functions
export RieszMatrix, omega, B, genfun, first_order

end


module Regularizer

export regularizer, createElasticOperatorCentered, createElasticOperatorStaggered, createDiffusiveOperatorCentered
export createCurvatureOperatorCentered

function regularizer(deformationField::Array{Float64,1}, operator)

    d2functionValue = operator'*operator
    dfunctionValue  = d2functionValue*deformationField
    functionValue   = (0.5 .* deformationField'*dfunctionValue)[1]
    return [functionValue, dfunctionValue, d2functionValue]

end

function createElasticOperatorCentered(h::Array{Float64,1},m::Array{Int64,1}; mu::Number = 1, lambda::Number = 0)

  m = m.-1

  a   = sqrt(mu)
  b   = sqrt(mu+lambda)
  dx(k) = spdiagm((-1.*ones(m[k]),ones(m[k])),0:1,m[k],m[k]+1)/h[k]
  av(k) = spdiagm((ones(m[k])/2,ones(m[k])/2),0:1,m[k],m[k]+1)
  D1   = kron(speye(m[2]+1),dx(1))
  D2   = kron(dx(2),speye(m[1]+1))
  A1   = kron( av(2) , speye(m[1]))
  A2   = kron( speye(m[2]),  av(1))
  p1,p2 = size(D1)
  p3,p4 = size(D2)

  B = [ a*D1 spzeros(p1,p2);
       a*D2 spzeros(p3,p4);
       spzeros(p1,p2) a*D1;
       spzeros(p3,p4) a*D2;
       b*A1*D1 b*A2*D2 ]

  return B

end

# function createElasticOperatorStaggered(h::Array{Float64,1},m::Array{Int64,1}; mu::Number = 1, lambda::Number = 0)
#
#     a   = sqrt(mu)
#     b   = sqrt(mu+lambda)
#
#     dx(h,m) = spdiagm((-ones(m,1),ones(m,1)),[0,1],m,m+1)/h
#
#     D11 = kron(dx(h[2],m[2]),speye(m[1]))
#     D21 = kron(speye(m[2]+1),dx(h[1],m[1]-1))
#     D12 = kron(dx(h[2],m[2]-1),speye(m[1]+1))
#     D22 = kron(speye(m[2]),dx(h[1],m[1]))
#
#     B = spzeros(2*size(D11,1)+size(D21,1)+size(D12,1)+size(D22,1),size(D11,2)+size(D12,2))
#     B[1:size(D11,1), 1:size(D11,2)]                         = a*D11
#     B[size(D11,1)+1:size(D11,1)+size(D21,1), 1:size(D11,2)] = a*D21
#     B[size(D11,1)+size(D21,1)+1:size(D11,1)+size(D21,1)+size(D12,1), size(D11,2)+1:end]                         = a*D12
#     B[size(D11,1)+size(D21,1)+size(D12,1)+1:size(D11,1)+size(D21,1)+size(D12,1)+size(D22,1), size(D11,2)+1:end] = a*D22
#     B[size(D11,1)+size(D21,1)+size(D12,1)+size(D22,1)+1:end, 1:size(D11,2)]     = b*D11
#     B[size(D11,1)+size(D21,1)+size(D12,1)+size(D22,1)+1:end, size(D11,2)+1:end] = b*D22
#
#     return B
#
# end

function createDiffusiveOperatorCentered(h::Array{Float64,1},m::Array{Int64,1})

  d(k) = spdiagm((-ones(m[k]-1,1),ones(m[k]-1,1)),[0 1],m[k]-1,m[k])/(h[k])
  dx = d(1)
  dy = d(2)
  D1 = kron(speye(m[2]),dx)
  D2 = kron(dy,speye(m[1]))
  p1,p2 = size(D1)
  p3,p4 = size(D2)
  B = [ D1 spzeros(p1,p2);
        D2 spzeros(p3,p4);
        spzeros(p1,p2) D1;
        spzeros(p3,p4) D2
      ]
end

function createCurvatureOperatorCentered(h::Array{Float64,1},m::Array{Int64,1})

  d(k) = spdiagm((-ones(m[k]-2,1),2*ones(m[k]-2,1), -ones(m[k]-2,1)),[0 1 2],m[k]-2,m[k])/(h[k])
  dx = d(1); #dx[1,1]=1; dx[end, end]=1
  dy = d(2); #dy[1,1]=1; dy[end, end]=1
  D1 = kron(speye(m[2]),dx)
  D2 = kron(dy,speye(m[1]))
  p1,p2 = size(D1)
  p3,p4 = size(D2)
  B = [ D1 spzeros(p1,p2);
        D2 spzeros(p3,p4);
        spzeros(p1,p2) D1;
        spzeros(p3,p4) D2
      ]
end

end#


module day1

using DelimitedFiles
readInput() = vec(readdlm(joinpath(@__DIR__, "input.txt"), Int))

fuelRequired(x) = x ÷ 3 - 2
part1(input) = sum(fuelRequired, input)

function fuelRequired2(x)
    fuel = fuelRequired(x)
    fuel > 0 ? fuel + fuelRequired2(fuel) : 0
end
part2(input) = sum(fuelRequired2, input)

end


export get_prop_mass,
       hohmann_circular_coplanar,
       hohmann_circular,
       hohmann_circle2circle,
       get_launch_asymptote,
       lambert,
       lambert_target,
       lambert_transfer

function get_prop_mass(m0, dV, Isp)
    # dV in m/s, Isp in s, m0 in kg
    return m0 * (1. - exp(-dV/(9.81*Isp)))
end

"Hohmann transfer between two circular orbits"
function hohmann_circle2circle(a1, a2, mu)
    aT = (a1 + a2)/2.
    vc1 = getv( mu, a1, a1 )
    vc2 = getv( mu, a2, a2 )

    vT1 = getv( mu, a1, aT )
    vT2 = getv( mu, a2, aT )

    dV1 = abs(vc1 - vT1)
    dV2 = abs(vc2 - vT2)

    return (aT, dV1, dV2)
end

function get_launch_asymptote(v_INF_HCI::Vector{S}, epoch) where {S<:AbstractFloat}
    T = get_hci2eci_matrix( epoch )
    v_INF_ECI = T*v_INF_HCI
    α = atan(v_INF_ECI[2],v_INF_ECI[1])
    δ = asin(v_INF_ECI[3]/norm(v_INF_ECI))
    return (α,δ)
end

"""
Compute the relevant quantities for a Hohmann transfer between objects
orbiting the Sun, assuming circular, coplanar orbits also assuming
circular parking orbit
"""
function hohmann_circular_coplanar(muDepart,
                                   aDepart,
                                   rPark,
                                   muDest,
                                   aDest,
                                   aCapture,
                                   rCapture)

    # v_INF_depart: v_infinity required wrt departure planet
    # v_INF_arrive: v_infinity wrt destination planet
    (aTrans, v_INF_depart, v_INF_arrive) = hohmann_circle2circle(aDepart, aDest, muSun)

    ### Departure

    # departure velocity wrt Sun:
    v_SC_S_depart = getv(muSun, aDepart, aTrans)

    # delta V required:
    dV_depart = getvhyp(muDepart, rPark, v_INF_depart) -
                sqrt(muDepart/rPark)

    ### Arrival

    # arrival velocity wrt Sun:
    v_SC_S_arrive = getv(muSun, aDest, aTrans)

    # delta V required for capture into orbit specified by aCapture and rCapture
    dV_cap = getArrivalDV( muDest, rCapture, aCapture, v_INF_arrive )

    TOF = getPeriod(muSun, aTrans)/2. # seconds

    return (v_INF_depart, dV_depart, v_INF_arrive, dV_cap, TOF)
end

function getArrivalDV(muDest, rCapture, aCapture, v_INF_arrive)
    return abs( getvhyp(muDest, rCapture, v_INF_arrive) -
               getv(muDest, rCapture, aCapture) )
end

"""
Compute the relevant quantities for a Hohmann transfer between objects
orbiting the Sun, assuming circular non-coplanar orbits
"""
function hohmann_circular(muDepart, aDepart, rPark, muDest,
                          aDest, dInc, aCapture, rCapture)

    aTrans = (aDepart + aDest)/2

    # Departure

    # departure velocity wrt Sun:
    v_SC_S_depart = getv(muSun, aDepart, aTrans)
    v_depart_S = sqrt(muSun/aDepart)

    # arrival velocity wrt Sun:
    v_SC_S_arrive = getv(muSun, aDest, aTrans)
    v_dest_S = sqrt(muSun/aDest)

    # v_infinity required
    v_INF_depart(alpha) = sqrt(v_SC_S_depart^2 + v_depart_S^2 -
                        2*v_SC_S_depart*v_depart_S*cos(alpha))
    v_INF_arrive(alpha) = sqrt(v_SC_S_arrive^2 + v_dest_S^2 -
                               2*v_SC_S_arrive*v_dest_S*cos(dInc - alpha))

    # numerically solve for the optimal plane change split
    f(beta) = v_SC_S_depart*v_depart_S*sin(beta)/v_INF_depart(beta) -
                v_dest_S*v_SC_S_arrive*sin(dInc - beta)/v_INF_arrive(beta)
    alpha1 = fzero(f,0.,dInc)

    # delta V required for departure:
    dV_depart = getvhyp(muDepart, rPark, v_INF_depart(alpha1))-
                sqrt(muDepart/rPark)

    # delta V required for capture into orbit specified by aCapture and rCapture
    dV_cap = getArrivalDV( muDest, rCapture, aCapture, v_INF_arrive(alpha1) )

    TOF = getPeriod(muSun, aTrans)/2. #seconds

    return (v_INF_depart(alpha1), dV_depart, v_INF_arrive(alpha1), dV_cap, alpha1, TOF)
end

"""
Translation of lambert_target.m

Original Matlab code provided by Brent Barbee
"""
function lambert_target(mu, TOF, r1::Vector{T}, r2::Vector{T},
                        v1::Vector{T}, v2::Vector{T}) where {T<:AbstractFloat}

    uu = cross(r1, r2)

    uh = (1/norm(uu))*uu

    (vi1, vf1) = lambert(mu, TOF, r1, r2, uh)

    tot_dv1 = norm(v1-vi1) + norm(vf1-v2)

    uu = cross(r2, r1)

    uh = (1/norm(uu))*uu

    (vi2, vf2) = lambert(mu, TOF, r1, r2, uh)

    tot_dv2 = norm(v1-vi2) + norm(vf2-v2)

    if tot_dv1 < tot_dv2

        vi = vi1
        vf = vf1

    else

        vi = vi2
        vf = vf2

    end
    return (vi, vf)
end

"""
Title     : Lambert
Purpose   : Given a conic gravity field, compute the required velocity
            to transfer between initial and final inertial position
            vectors in a specified transfer time interval.
Inputs    : mu  - (km^3/s^2) Gravitational constant
            dt  - (s)        Transfer time interval
            ri  - (km)       Init. inertial position vector
            rf  - (km)       Final inertial position vector
            uh  - (na)       Unit angular momentum vector
Outputs   : vi  - (km/s)     Init. inertial velocity vector (required)
            vf  - (km/s)     Final inertial velocity vector
Comments  : 1) This routine does not have a multi-revolution capability
            2) The unit angular momentum vector determines the plane,
               and direction, of the transfer
Reference : Shepperd, S.W., unpublished notes.
Original author: Brent Barbee
"""
function lambert(mu, dt, ri::Vector{T}, rf::Vector{T},
                 uh::Vector{T}) where {T<:AbstractFloat}

    ### Initialization Iteration Loop

    r0 = ri - dot(ri, uh) * uh
    r1 = rf - dot(rf, uh) * uh

    if abs(dot(uh, uh) - 1) > 10000 * eps()
        error("Lambert : Aborting : Normal vector not unitary\n")
    end

    if abs(dot(ri, uh)) > 0.00001
        error("Lambert : Warning : Init. position not in plane\n")
    end

    if abs(dot(rf, uh)) > 0.00001
        error("Lambert : Warning : Final position not in plane\n")
    end

    imax      = 20
    maxu      = typemax(Int64)
    tolerance = 1000 * eps()

    m0 = norm(r0)
    m1 = norm(r1)
    cc = norm(r1 - r0)
    ss = (m0 + m1 + cc) / 2

    rc = ss / 2
    vc = sqrt(mu / rc)
    wc = vc / rc

    k1 = sqrt(abs((ss - m0) * (ss - m1)))
    k  = dot(cross(r0, r1), uh)

    if  (k1 * k1) > (ss * (ss - cc))
        k  = k / (2 * k1 * ss)
        k2 = 1 - k * k
    else
        k2 = cc / ss
        if k >= 0
            k  = +sqrt(1 - k2)
        else
            k  = -sqrt(1 - k2)
        end
    end

    tdesired = wc * dt

    if tdesired > 4 * (1 - k * k * k) / 3
        u = 0
        umin = -1
        umax = 1
    else
        u = 1
        umin = 1
        umax = maxu
    end

    ### Iterate Until Transfer Time Matches

    # initialize values outside of loop
    q = 0.
    y = 0.
    h1 = 0.
    h0 = 0.
    uu = 0.
    pp = 0.
    dt = 0.
    slope = 0.
    terror = 0.
    uold = 0.0
    dtold = 0.0

    for i=1:imax

        q = k * u
        y = sqrt(q * q + k2)

        if q <= 0
            h1 = y - q
        else
            h1 = k2 / (y + q)
        end

        h0 = k + u * h1
        q  = (1 - abs(h0)) / 2
        uu = (16 / 15) * h1 ^ 5 * cfgauss(5, q)

        if h0 < 0
            pp = 2 * pi / sqrt(1 - u * u)^5
            uu = pp - uu
        end

        dt     = 4 * h1 * (k + h0 * h1 * h1 / 3) + (1 - u * u) * uu
        slope  = 3 * u * uu - 4 * (h1 / y) * (k * k + (k * h0 + h1 * h1) * h1 * h1)
        terror = tdesired - dt

        if abs(terror) < wc
            if abs(terror) < tolerance * abs(tdesired )
                break
            end

            if abs(terror) < tolerance * abs(slope * u)
                break
            end
        end

        if (i > 1) && (u  ==  uold)
            break
        end

        if (i > 1) && (dt == dtold)
            break
        end

        uold  = u
        dtold = dt
        ustep = terror / slope

        if ustep > 0
            umin = u
            u    = u + ustep
            if u > umax
                u = (umin + umax) / 2
            end
        else
            umax = u
            u    = u + ustep
            if u < umin
                u = (umin + umax) / 2
            end
        end

    end

    ### Final Computations

    h1 = h1 / vc
    uu = uu / vc^5

    h  = k1 / h1
    n0 = +(k * ss - m0 * h0) / h1
    n1 = -(k * ss - m1 * h0) / h1
    v0 = (n0 * r0 + h * cross(uh, r0)) / (m0 * m0)
    v1 = (n1 * r1 + h * cross(uh, r1)) / (m1 * m1)

    vi  = v0
    vf  = v1

    return (vi, vf)
end

"""
Title     : CF Gauss
Purpose   : Compute the Gaussian continued fraction.
Inputs    : i - (na) Order of continued fraction
            q - (na) Argument of continued fraction
Outputs   : g - (na) Continued fraction result
Comments  : The continued fraction is defined as the ratio of two
            hypergeometric functions as follows:

                                   F(i,1,1+(i/2),q)
                g = G(i,0,i/2,q) = ----------------
                                     F(i,0,i/2,q)

Exception : Illegal argument
Reference : Shepperd, S.W., "Universal Keplerian State Transition
            Matrix," Celestial Mechanics, Vol. 35, 1985.
Original author: Brent Barbee
"""
function cfgauss(i, q)

    if q > 0.5 + 16 * eps()
        error("CF Gauss : Illegal argument\n\n")
    end

    g = 1
    r = 1
    s = 1
    n = 0
    l = i - 2
    d = i * (i - 2)
    k = 1 - 2 * i

    while(true)

        k    = -k
        l    = l + 2
        d    = d + 4 * l
        n    = n + (1 + k) * l
        r    = d / (d - n * r * q)
        s    = (r - 1) * s
        gold = g
        g    = gold + s

        if g == gold
            break
        end

    end

    return g
end

"""
Wrapper to get delta V values for a Lambert-targeted transfer
assumes Sun as the central body

Units are km and s
"""
function lambert_transfer(muDepart,
                         TOF,
                         launchEpoch,
                         rPark,
                         x1_HCI::Vector{T}, x2_HCI::Vector{T},
                         muDest = 0.,
                         rCapture = 1000.0,
                         aCapture = 1000.0 ) where {T<:AbstractFloat}
    (v_SC_S_depart_HCI, v_SC_S_arrive_HCI) = lambert_target( muSun,
                                                             TOF,
                                                             x1_HCI[1:3], x2_HCI[1:3],
                                                             x1_HCI[4:6], x2_HCI[4:6] )
    # departure:
    v_INF_E_HCI = v_SC_S_depart_HCI - x1_HCI[4:6]
    C3_depart = norm(v_INF_E_HCI)^2
    dV_depart = abs(getvhyp(muEarth, rPark, norm(v_INF_E_HCI)) - getv(muEarth, rPark, rPark))

    (RLA,DLA) = get_launch_asymptote( v_INF_E_HCI, launchEpoch )

    # arrival:
    v_INF_arrive = norm(v_SC_S_arrive_HCI - x2_HCI[4:6])
    dV_arrive = abs(getArrivalDV( muDest, rCapture, aCapture, v_INF_arrive ))

    return (dV_depart, dV_arrive, C3_depart, RLA, DLA)
end


# Mathematical Constants
export RAD2DEG
"""
Constant to convert radians to degrees. Equal to 360/2pi. [deg/rad]
"""
const RAD2DEG = 360.0/(2.0*pi)

export DEG2RAD
"""
Constant to convert radians to degrees. Equal to 2pi/360. [rad/deg]
"""
const DEG2RAD = 2.0*pi/360.0

export AS2RAD
"""
Constant to convert arcseconds to radians. Equal to 2pi/(360*3600). [rad/as]
"""
const AS2RAD = 2.0*pi/360.0/3600.0

export RAD2AS
"""
Constant to convert radians to arcseconds. Equal to 2pi/(360*3600) [as/ras]
"""
const RAD2AS = 360.0*3600.0/pi/2.0

# Physical Constants
export C_LIGHT
"""
Speed of light in vacuum. [m/s]

D. Vallado, _Fundamentals of Astrodynamics and Applications_ (4th Ed.), 2010
"""
const C_LIGHT     = 299792458.0                 # [m/s]Exact definition Vallado

export AU
"""
Astronomical Unit. Equal to the mean distance of the Earth from the sun.
TDB-compatible value. [m]

P. Gérard and B. Luzum, IERS Technical Note 36, 2010
"""
const AU          = 1.49597870700e11            # [m] Astronomical Unit IAU 2010

# Time Consants

export SECONDS_IN_DAY
"""
Number of seconds in a day.
"""
const SECONDS_IN_DAY = 86400

export MJD_ZERO
"""
Offset of Modified Julian Days representation with respect to Julian Days. For
a time, t, MJD_ZERO is equal to:

    MJD_ZERO = t_jd - t_mjd

Where t_jd is the epoch represented in Julian Days, and t_mjd is the epoch in
Modified Julian Days.

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const MJD_ZERO = 2400000.5

export MJD2000
"""
Modified Julian Date of January 1, 2000 00:00:00. Value is independent of time
scale.

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const MJD2000  = 51544.0

export GPS_TAI
"""
Offset of GPS time system with respect to TAI time system.

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const GPS_TAI  = -19.0

export TAI_GPS
"""
Offset of TAI time system with respect to GPS time system.

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const TAI_GPS  = -GPS_TAI

export TT_TAI
"""
Offset of TT time system with respect to TAI time system.

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const TT_TAI   = 32.184

export TAI_TT
"""
Offset of TAI time system with respect to TT time system.

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const TAI_TT   = -TT_TAI

export GPS_TT
"""
Offset of GPS time system with respect to TT time system.

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const GPS_TT   = GPS_TAI + TAI_TT

export TT_GPS
"""
Offset of TT time system with respect to GPS time system.

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const TT_GPS   = -GPS_TT

export GPS_ZERO
"""
Modified Julian Date of the start of the GPS time system in the GPS time system.
This date was January 6, 1980 0H as reckond in the UTC time system.

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const GPS_ZERO = 44244.0

# Earth Constants
export R_EARTH
"""
Earth's equatorial radius. [m]

GGM05s Gravity Model
"""
const R_EARTH     = 6.378136300e6               # [m] GGM05s Value

export WGS84_a
"""
Earth's semi-major axis as defined by the WGS84 geodetic system. [m]

NIMA Technical Report TR8350.2
"""
const WGS84_a     = 6378137.0                   # WGS-84 semi-major axis

export WGS84_f
"""
Earth's ellipsoidal flattening.  WGS84 Value.

NIMA Technical Report TR8350.2
"""
const WGS84_f     = 1.0/298.257223563           # WGS-84 flattening

export GM_EARTH
"""
Earth's Gravitational constant [m^3/s^2]

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const GM_EARTH    = 3.986004415e14              # [m^3/s^2] GGM05s Value

export e_EARTH
"""
Earth's first eccentricity. WGS84 Value. [dimensionless]

NIMA Technical Report TR8350.2
"""
const e_EARTH     = 8.1819190842622e-2          # [] First Eccentricity WGS84 Value

export J2_EARTH
"""
Earth's first zonal harmonic. [dimensionless]

GGM05s Gravity Model.
"""
const J2_EARTH    = 0.0010826358191967          # [] GGM05s value

export OMEGA_EARTH
"""
Earth axial rotation rate. [rad/s]

D. Vallado, _Fundamentals of Astrodynamics and Applications_ (4th Ed.), p. 222, 2010
"""
const OMEGA_EARTH = 7.292115146706979e-5        # [rad/s] Taken from Vallado 4th Ed page 222

# Sun Constants
export GM_SUN
"""
Gravitational constant of the Sun. [m^3/s^2]

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const GM_SUN      = 132712440041.939400*1e9     # Gravitational constant of the Sun

export R_SUN
"""
Nominal solar photospheric radius. [m]

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const R_SUN       = 6.957*1e8                   # Nominal solar radius corresponding to photospheric radius

export P_SUN
"""
Nominal solar radiation pressure at 1 AU. [N/m^2]

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const P_SUN       = 4.560E-6                    # [N/m^2] (~1367 W/m^2) Solar radiation pressure at 1 AU

# Celestial Constants - from JPL DE430 Ephemerides
export GM_MOON
"""
Gravitational constant of the Moon. [m^3/s^2]

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const GM_MOON     = 4902.800066*1e9

export GM_MERCURY
"""
Gravitational constant of the Mercury. [m^3/s^2]

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const GM_MERCURY  = 22031.780000*1e9

export GM_VENUS
"""
Gravitational constant of the Venus. [m^3/s^2]

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const GM_VENUS    = 324858.592000*1e9

export GM_MARS
"""
Gravitational constant of the Mars. [m^3/s^2]

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const GM_MARS     = 42828.37521*1e9

export GM_JUPITER
"""
Gravitational constant of the Jupiter. [m^3/s^2]

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const GM_JUPITER  = 126712764.8*1e9

export GM_SATURN
"""
Gravitational constant of the Saturn. [m^3/s^2]

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const GM_SATURN   = 37940585.2*1e9

export GM_URANUS
"""
Gravitational constant of the Uranus. [m^3/s^2]

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const GM_URANUS   = 5794548.6*1e9

export GM_NEPTUNE
"""
Gravitational constant of the Neptune. [m^3/s^2]

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const GM_NEPTUNE  = 6836527.100580*1e9

export GM_PLUTO
"""
Gravitational constant of the Pluto. [m^3/s^2]

O. Montenbruck, and E. Gill, _Satellite Orbits: Models, Methods and
Applications_, 2012.
"""
const GM_PLUTO    = 977.000000*1e9


# ---
# title: 29. Divide Two Integers
# id: problem29
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Math, Binary Search
# link: <https://leetcode.com/problems/divide-two-integers/description/>
# hidden: true
# ---
#
# Given two integers `dividend` and `divisor`, divide two integers without using
# multiplication, division, and mod operator.
#
# Return the quotient after dividing `dividend` by `divisor`.
#
# The integer division should truncate toward zero, which means losing its
# fractional part. For example, `truncate(8.345) = 8` and `truncate(-2.7335) =
# -2`.
#
# **Note:**
#
#   * Assume we are dealing with an environment that could only store integers within the 32-bit signed integer range: [−231,  231 − 1]. For this problem, assume that your function **returns 2 31 − 1 when the division result overflows**.
#
#
#
# **Example 1:**
#
#
#
#     Input: dividend = 10, divisor = 3
#     Output: 3
#     Explanation: 10/3 = truncate(3.33333..) = 3.
#
#
# **Example 2:**
#
#
#
#     Input: dividend = 7, divisor = -3
#     Output: -2
#     Explanation: 7/-3 = truncate(-2.33333..) = -2.
#
#
# **Example 3:**
#
#
#
#     Input: dividend = 0, divisor = 1
#     Output: 0
#
#
# **Example 4:**
#
#
#
#     Input: dividend = 1, divisor = 1
#     Output: 1
#
#
#
#
# **Constraints:**
#
#   * `-231 <= dividend, divisor <= 231 - 1`
#   * `divisor != 0`
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


#=
Illustrates vectors in the plane.

@author : Spencer Lyon <spencer.lyon@nyu.edu>
          Victoria Gregory <victoria.gregory@nyu.edu>

@date: 07/09/2014
=#

using Plots
pyplot()
using LaTeXStrings

function plane_fig()

  vecs = ([2, 4], [-3, 3], [-4, -3.5])
  x_vals = zeros(2, length(vecs))
  y_vals = zeros(2, length(vecs))
  labels = []

  # Create matrices of x and y values, labels for plotting
  for i = 1:length(vecs)
    v = vecs[i]
    x_vals[2, i] = v[1]
    y_vals[2, i] = v[2]
    labels = [labels; (1.1 * v[1], 1.1 * v[2], "$v")]
  end

  plot(x_vals, y_vals, arrow=true, color=:blue,
       legend=:none, xlims=(-5, 5), ylims=(-5, 5),
       annotations = labels, xticks=-5:1:5, yticks=-5:1:5)
  vline!([0], color=:black)
  hline!([0], color=:black)
  plot!(foreground_color_axis=:white, foreground_color_text=:white,
        foreground_color_border=:white)

end

function scalar_multiply()  # illustrate scalar multiplication

  x = [2, 2]
  scalars = [-2, 2]

  # Create matrices of x and y values, labels for plotting
  x_vals = zeros(2, 1 + length(scalars))
  y_vals = zeros(2, 1 + length(scalars))
  labels = []
  x_vals[2, 3] = x[1]
  y_vals[2, 3] = x[2]
  labels = [labels; (x[1] + 0.4, x[2] - 0.2, L"$x$")]

  # Perform scalar multiplication, store results in plotting matrices
  for i = 1:length(scalars)
    s = scalars[i]
    v = s .* x
    x_vals[2, i] = v[1]
    y_vals[2, i] = v[2]
    labels = [labels; (v[1] + 0.4, v[2] - 0.2, LaTeXString("\$$s x\$"))]
  end

  plot(x_vals, y_vals, arrow=true, color=[:red :red :blue],
       legend=:none, xlims=(-5, 5), ylims=(-5, 5),
       annotations = labels, xticks=-5:1:5, yticks=-5:1:5)
  vline!([0], color=:black)
  hline!([0], color=:black)
  plot!(foreground_color_axis=:white, foreground_color_text=:white,
        foreground_color_border=:white)

end

# Plot the first figure --- three vectors in the plane

plane_fig()


module Probability

#Binomial Theorem
function KSuccess(n, k, pSuccess)
    possibilities = Binomial(n,k)
    nCk =  n == k ? 1 : possibilities
    probability = nCk * pSuccess ^ k * (1 - pSuccess) ^ (n-k)

    return probability
end

function KorMoreSuccess(n, k, pSuccess)
    totalProb = Float64(0)
    for i in [k:n;]
        prob = KSuccess(n, i, pSuccess)
        totalProb = totalProb + prob
    end

    return totalProb
end

function Binomial(n,k)
    return factorial(BigInt(n))/(factorial(BigInt(n-k)) * factorial(BigInt(k)))
end

end


using DifferentialForms

# Create a 0-form (a scalar):
e = one(Form{3})
@test e[] == 1
@test collect(e) == [1]

# Create a 1-form (a vector) from a tuple:
v = Form{3,1}((1, 2, 3))
@test v[1] == 1
@test v[2] == 2
@test v[3] == 3
@test collect(v) == [1, 2, 3]

# Create a 2-form (an axial vector) from a tuple:
a = Form{3,2}((1, 2, 3))
@test a[1, 2] == 1
@test a[1, 3] == 2
@test a[2, 3] == 3
@test collect(a) == [1, 2, 3]

# Create a 3-form (a pseudoscalar):
p = Form{3,3}((1,))
@test p[1, 2, 3] == 1
@test p[end] == 1
@test collect(p) == [1]

# Add, subtract, scale
a2 = 2 * a
a3 = a + a2
@test a3 == 3 * a

# Exterior product (type \wedge<tab>)
# q = wedge(v, a)
q = v ∧ a
@test q == Form{3,3}((2,))

# Dot product (type \cdot<tab>)
# b = dot(v, v)
b = v ⋅ v
@test b == 14 * one(Form{3})
c = a ⋅ a
@test c == 14 * one(Form{3})

# Cross product (type \times<tab>)
# w = cross(a, a)
w = v × v
@test w == zero(Form{3,1})


## SVD on an image
using LinearAlgebra
using Images
im = load("low-rank-image.png")
A = Float64.(Gray.(im))
A = 1.0.-A # invert the colors
## Show the image inverted
Gray.(A)
## Compute the SVD
U,S,V = svd(A)

## setup interact
using Interact
ui = button()
settheme!(:nativehtml)
display(ui)

## Show the best rank-k approximations
mp = @manipulate for k in 1:25
    X = U[:,1:k]*Diagonal(S[1:k])*V[:,1:k]'
    Gray.(X)
end

##


function triangle_centrality1(A)
    T = mul(A, A', Semirings.PLUS_TIMES[Float64], mask=A)
    y = reduce(Monoids.PLUS_MONOID[Float64], T, dims=2)
    k = reduce(Monoids.PLUS_MONOID[Float64], y)
    return (3 * mul(A, y) - 2 * mul(T, y) .+ y) ./ k
end

function triangle_centrality2(A)
    T = mul(A, A', Semirings.PLUS_PAIR, mask=A, desc=Descriptors.S)
    y = reduce(Monoids.PLUS_MONOID[Float64], T, dims=2)
    k = reduce(Monoids.PLUS_MONOID[Float64], y)
    return map(BinaryOps.TIMES, eadd(eadd(3.0 * mul(A, y), 2.0 * mul(T, y, Semirings.PLUS_SECOND), BinaryOps.MINUS), y), 1/k)
end


using Printf, LinearAlgebra
import FixedPointNumbers: N0f8
import Images
import CSV, DataFrames
import Cairo

toRadians(α::Float64) = α / 180.0 * π
toDegrees(α::Float64) = α * 180.0 / π

#===========================================
  ____            _   _ _   _ _
 / ___| ___  ___ | | | | |_(_) |___
| |  _ / _ \/ _ \| | | | __| | / __|
| |_| |  __/ (_) | |_| | |_| | \__ \
 \____|\___|\___/ \___/ \__|_|_|___/

=============================================#

struct PositionLLH
    lat::Float64
    lon::Float64
    height::Float64
end

struct PositionXYZ
    x::Float64
    y::Float64
    z::Float64
end

abstract type Ellipsoid
end

struct Wgs84 <: Ellipsoid
    a::Float64
    b::Float64
    f::Float64
    e::Float64
    e²::Float64
    function Wgs84()
        a  = 6378137.0
        f  = 1.0/298.257223563
        b  = a*(1.0-f)
        e² = 1.0 - b*b/(a*a) # or f * (2.0-f)
        e  = sqrt(e²)
        new(a,b,f,e,e²)
    end
end

struct SphericalEarth <: Ellipsoid
    r::Float64
    SphericalEarth() = new(6378137.0)
end

function llh_to_xyz(ellipsoid::Wgs84, llh::PositionLLH)::PositionXYZ
    Φ    = toRadians(llh.lat)
    λ    = toRadians(llh.lon)
    h    = llh.height
    e²   = ellipsoid.e²
    sinΦ = sin(Φ)
    cosΦ = cos(Φ)
    sinλ = sin(λ)
    cosλ = cos(λ)
    v = ellipsoid.a / sqrt(1.0 - e² * sinΦ * sinΦ)
    x = (v+h) * cosΦ * cosλ
    y = (v+h) * cosΦ * sinλ
    z = ((1.0 - e²) * v + h) * sinΦ;
    return PositionXYZ(x,y,z)
end

# TODO: make it work for south and west
function xyz_to_llh(ellipsoid::Wgs84, xyz::PositionXYZ)::PositionLLH
    e²       = ellipsoid.e²
    p        = sqrt(xyz.x*xyz.x + xyz.y*xyz.y)
    lon      = atan(xyz.y / xyz.x)
    lat_init = atan(xyz.z/(p*(1.0 - e²)))
    v        = ellipsoid.a / sqrt(1.0-e²*sin(lat_init)*sin(lat_init))
    lat      = atan((xyz.z + e²*v*sin(lat_init))/p)
    height   = (p/cos(lat))-v
    return PositionLLH(toDegrees(lat), toDegrees(lon), height)
end

function llh_to_xyz(ellipsioid::SphericalEarth, llh::PositionLLH)::PositionXYZ
    Φ    = toRadians(llh.lat)
    λ    = toRadians(llh.lon)
    h    = llh.height
    cosΦ = cos(Φ)
    v    = ellipsioid.r + h
    x    = v * cosΦ * cos(λ)
    y    = v * cosΦ * sin(λ)
    z    = v * sin(Φ)
    return PositionXYZ(x,y,z)
end

function xyz_to_llh(ellipsoid::SphericalEarth, xyz::PositionXYZ)::PositionLLH
    v      = sqrt(xyz.x*xyz.x + xyz.y*xyz.y + xyz.z*xyz.z)
    height = v - ellipsoid.r
    lon    = atan(xyz.y, xyz.x)
    lat    = asin(xyz.z/v)
    return PositionLLH(toDegrees(lat), toDegrees(lon), height)
end

# https://www.movable-type.co.uk/scripts/latlong.html
# for SphericalEarth, but best source found
function bearing(p1::PositionLLH, p2::PositionLLH)::Float64
    φ1 = toRadians(p1.lat)
    φ2 = toRadians(p2.lat)
    Δλ = toRadians(p2.lon-p1.lon)
    y  = sin(Δλ)*cos(φ2)
    x  = cos(φ1)*sin(φ2) - sin(φ1)*cos(φ2)*cos(Δλ)
    θ  = atan(y, x)
    return mod(toDegrees(θ) + 360.0, 360.0)
end

xyz_to_vector(xyz::PositionXYZ)::Vector{Float64} = [xyz.x; xyz.y; xyz.z]

# GeoUtils test
#testXYZ = llh_to_xyz(wgs84, PositionLLH(49.0, 16.0, 225.0))
#println(testXYZ)
#testLLH= xyz_to_llh(wgs84, testXYZ)
#println(testLLH)

#================================================================
 _   _      _       _     _   __  __
| | | | ___(_) __ _| |__ | |_|  \/  | __ _ _ __
| |_| |/ _ \ |/ _` | '_ \| __| |\/| |/ _` | '_ \
|  _  |  __/ | (_| | | | | |_| |  | | (_| | |_) |
|_| |_|\___|_|\__, |_| |_|\__|_|  |_|\__,_| .__/
              |___/                       |_|
===============================================================#

struct LatLonRange
    minLat::Int16
    minLon::Int16
    maxLat::Int16
    maxLon::Int16
end

function loadData(range::LatLonRange, tileDir)
    getHgtFileName(lat, lon) = @sprintf "N%02dE%03d.hgt" lat lon
    getHgtFilePath(lat, lon, tileDir) = @sprintf "%s/%s" tileDir getHgtFileName(lat, lon)

    nTilesHoriz = range.maxLon - range.minLon + 1
    nTilesVert  = range.maxLat - range.minLat + 1
    nTilesTotal = nTilesHoriz * nTilesVert
    dataWidth   = nTilesHoriz*1200+1
    dataHeight  = nTilesVert*1200+1

    @printf("Requesting data for area %d°N %d°E - %d°N %d°E ... (aprox. %3.0fx%3.0f km).\n",
        range.minLat, range.minLon,
        range.maxLat, range.maxLon,
        (nTilesHoriz)*111.1*cos(range.minLat/180.0*π),
        (nTilesVert)*111.1
        )
    @printf("I will read %dx%d=%d tiles, heightmap size is %dx%d (%d MB).\n",
        nTilesHoriz, nTilesVert, nTilesTotal,
        dataWidth, dataHeight, dataWidth*dataHeight*2/1000000
        )

    # Note: array is indexed by row, column and starting index is 1
    data = Array{UInt16}(undef, dataHeight, dataWidth)
    progress = 0
    progressLock = Threads.SpinLock()
    Threads.@threads for i in 0:(nTilesTotal-1)
        lat = range.minLat + div(i, nTilesHoriz)
        lon = range.minLon + mod(i, nTilesHoriz)
        # Print progress
        lock(progressLock) do
            progress = progress + 1
            @printf("Loading tile %03d/%03d lat=%02d, lon=%02d\n", progress, nTilesTotal, lat, lon)
        end
        # Load tile
        tile = Array{Int16}(undef, 1201, 1201)
        path = getHgtFilePath(lat, lon, tileDir)
        io = open(path, "r")
        read!(io, tile)
        close(io)
        # Fix endianity, clamp, transpose and copy to data
        tile .= ntoh.(tile)
        clamp!(tile, 0, 6000)
        tile = transpose(tile)
        rOffset = (range.maxLat-lat) * 1200
        cOffset = (lon-range.minLon) * 1200
        for r in 1:1201
            @simd for c in 1:1201
                data[rOffset+r, cOffset+c] = tile[r, c]
            end
        end
    end
    return data
end

function saveHeightMap(data)
   maxValue = maximum(data)
   norm = Images.Gray.(data/maxValue)
   println("Saving heightmap-gray.png")
   save("heightmap-gray.png", norm)
end

# getHeight(srtmRange, heightMap, 49.142158, 16.627978) -> 192 Svratka, Svitava
function getHeight(range::LatLonRange, data::Matrix{UInt16}, lat::Float64, lon::Float64)
    r = Int64(trunc((range.maxLat+1 - lat)*1200))
    c = Int64(trunc((lon-range.minLon)*1200))
    r = clamp(r, 1, size(data)[1])
    c = clamp(c, 1, size(data)[2])
    return data[r,c]
end

makeEarthCurve(radius, distMax, distStep) = [sqrt(radius*radius-x*x)-radius for x=range(0, distMax, step=distStep)]

struct ViewPort
    ellipsoid::Ellipsoid
    eye::PositionLLH

    angleMin::Float64
    angleMax::Float64
    vertAngleMin::Float64
    vertAngleMax::Float64
    angleStep::Float64

    distMax::Float64
    distStep::Float64

    refractionCoef::Float64

    vUp::Vector{Float64}
    vNorth::Vector{Float64}
    vEast::Vector{Float64}

    outWidth::Int
    outHeight::Int

    function ViewPort(ellipsoid::Ellipsoid, eye::PositionLLH, azimuthMinR, azimuthMaxR, elevationMinR, elevationMaxR, angularStepR, distMaxM, refractionCoef)
        distStep = 50.0
        pRef   = xyz_to_vector(llh_to_xyz(ellipsoid, eye))
        vZ     = [0.0; 0.0; 1.0]
        vUp    = normalize(pRef)
        vEast  = normalize(cross(-vUp,vZ))
        vNorth = normalize(cross(vEast,-vUp))

        azimuthDiff = abs(azimuthMinR-azimuthMaxR)
        if (azimuthDiff > 2.0*π)
            throw(DomainError(azimuthDiff, "Azimuth difference should not exceed 2π"))
        end
        if (azimuthMinR > azimuthMaxR)
            azimuthMinR = azimuthMinR - 2.0*π
        end
        if (azimuthMinR < 0.0 && azimuthMaxR < 0.0)
            azimuthMinR = azimuthMinR + 2.0*π
            azimuthMaxR = azimuthMaxR + 2.0*π
        end
        xMax        = Int64(trunc( (azimuthMaxR-azimuthMinR)/angularStepR ))+1
        outWidth    = xMax+1
        outHeight   = size(range(elevationMinR, elevationMaxR, step=angularStepR))[1]+1
        new(ellipsoid, eye,
            azimuthMinR, azimuthMaxR, elevationMinR, elevationMaxR, angularStepR,
            distMaxM, distStep,
            refractionCoef,
            vUp, vNorth, vEast,
            outWidth, outHeight)
    end
end

function eyeVec(vp::ViewPort)::Vector{Float64}
    eyeXYZ = llh_to_xyz(vp.ellipsoid, vp.eye)
    return [eyeXYZ.x; eyeXYZ.y; eyeXYZ.z]
end

function makeDistMap(vp::ViewPort, latLonRange::LatLonRange, heightMap::Matrix{UInt16})::Matrix{UInt16}
    pRef = eyeVec(vp)
    earthRadius = sqrt(dot(pRef, pRef)) * vp.refractionCoef
    earthCurve  = makeEarthCurve(earthRadius, vp.distMax, vp.distStep)
    @printf("Earth radius is %6.1f km (refraction x%4.2f)\n", earthRadius/vp.refractionCoef/1000.0, vp.refractionCoef)
    @printf("Output size is %d x %d pixels\n", vp.outWidth, vp.outHeight)
    @printf("Output resolution is %f mrad per pixel or %f pixels per degree\n", vp.angleStep * 1000.0, 1.0/toDegrees(vp.angleStep))
    output      = zeros(UInt16, vp.outHeight, vp.outWidth)
    distances   = range(0.0, vp.distMax, step=vp.distStep)
    Threads.@threads for x in 0:(vp.outWidth-1)
        azimuth = vp.angleMin + x*vp.angleStep
        cosAz   = cos(azimuth)
        sinAz   = sin(azimuth)

        vertAngle     = vp.vertAngleMin
        h0            = vp.eye.height
        rayCastHeight = h0
        index         = 1
        direction     = vp.vNorth*cosAz + vp.vEast*sinAz
        point = [0.0,0.0,0.0]
        for dist in distances
            #point = pRef + dist * direction;
            #point = [pRef[1]+dist*direction[1], pRef[2]+dist*direction[2], pRef[3]+dist*direction[3]]
            point[1] = pRef[1]+dist*direction[1]
            point[2] = pRef[2]+dist*direction[2]
            point[3] = pRef[3]+dist*direction[3]
            llh = xyz_to_llh(vp.ellipsoid, PositionXYZ(point[1], point[2], point[3]))
            rayCastHeight = h0 + sin(vertAngle) * dist
            terrainHeight = earthCurve[index] + getHeight(latLonRange, heightMap, llh.lat, llh.lon)
            if terrainHeight > rayCastHeight
                newVertAngle = atan((terrainHeight-h0)/dist)
                yTop = Int64(trunc( (vp.vertAngleMax-newVertAngle)/vp.angleStep ))
                yBot = Int64(trunc( (vp.vertAngleMax-   vertAngle)/vp.angleStep ))
                v = UInt16(trunc( dist / vp.distStep ))
                for y in yTop:yBot
                    output[y, x+1] = v
                end
                vertAngle = newVertAngle
            end
            index = index + 1
        end
    end
    return output
end

function extractOutlines(distMap::Matrix{UInt16})
    nRows = size(distMap)[1]
    nCols = size(distMap)[2]
    #ouput = Array{Int16}(undef, nRows, nCols)
    output = zeros(N0f8, nRows, nCols)
    Threads.@threads for row in 2:nRows
        for col in 1:nCols
            diff::UInt8 = 255-clamp(abs(reinterpret(Int16,distMap[row-1,col]) - reinterpret(Int16,distMap[row,col])), 0, 255)
            output[row, col] = reinterpret(N0f8, diff)
        end
    end
    return output
end

function testPixel(distMap::Matrix{UInt16}, x::UInt64, y::UInt64, radius::Int64, value::UInt16, valueTolerance::UInt16)::Bool
    if (x < radius+1) || (y < radius+1) || (x+radius> size(distMap)[2]) || (y+radius> size(distMap)[1])
        return false
    end
    for row in (y-radius):(y+radius)
        for col in (x-radius):(x+radius)
            mapValue = distMap[row, col]
            if (mapValue > value) && ((mapValue - value) ≤ valueTolerance)
                return true
            end
            if (mapValue < value) && ((value - mapValue) ≤ valueTolerance)
                return true
            end
        end
    end
    return false
end

function drawSummits(vp::ViewPort, distMap::Matrix{UInt16})
    println("Loading data")
    dfFiltered = DataFrames.DataFrame(Summit = String[], Elevation = Float64[], Distance=Float64[], X=UInt64[], Y=UInt64[])

    # TODO: options
    #hillsCZ = CSV.File("data-cz-prom100.tsv") |> DataFrames.DataFrame
    #hillsSK = CSV.File("data-sk-prom200.tsv") |> DataFrames.DataFrame
    #hills = vcat(hillsCZ, hillsSK)
    hills =  CSV.File("osm-cz-sk.tsv") |> DataFrames.DataFrame
    # convert to ours azimuth, angle above horizon and distance - project into
    hill_to_xyz(ellipsoid::Ellipsoid, dfRow)::PositionXYZ = llh_to_xyz(ellipsoid, PositionLLH(dfRow["Latitude"], dfRow["Longitude"], dfRow["Elevation"]))
    # difference between true and seen earth curvature
    elevationDropAtDistance(distance::Float64, radius::Float64)::Float64 = sqrt(radius*radius-distance*distance)-radius
    elevationDropCompensation(distance::Float64, radius::Float64, refractionCoef::Float64)::Float64 = elevationDropAtDistance(distance, radius*refractionCoef) - elevationDropAtDistance(distance, radius)
    # TODO: make distance function
    mLocalToWorld = hcat(vp.vEast,vp.vNorth,vp.vUp)
    mWorldToLocal = inv(mLocalToWorld)
    #ground = PositionLLH(vp.eye.lat, vp.eye.lon, 0.0)
    pRef = xyz_to_vector(llh_to_xyz(vp.ellipsoid, vp.eye))
    earthRadius = sqrt(dot(pRef, pRef))
    for hill in eachrow(hills)
        hill_world = hill_to_xyz(vp.ellipsoid, hill)
        hill_local_xyz = mWorldToLocal * [hill_world.x; hill_world.y; hill_world.z]
#        hill_local_xyz[3] = hill_local_xyz[3] - earthRadius  <---- this gives weird altitutude, let's restore it in the same way as raytracer
        hill_local_xyz[3] = 0.0
        distance = sqrt(dot(hill_local_xyz, hill_local_xyz))
        if distance > vp.distMax
            continue
        end
        hill_local_xyz[3] = hill["Elevation"] + elevationDropAtDistance(distance, earthRadius * vp.refractionCoef) - vp.eye.height
        azimuth = bearing(vp.eye, PositionLLH(hill["Latitude"], hill["Longitude"], 0.0))
        if (azimuth < toDegrees(vp.angleMin) || azimuth > toDegrees(vp.angleMax)) # FIXME: test for weird angles
            continue
        end
        @printf("%20s is possibly visible at azimuth %6.2f, distance %6.2f km", hill["Summit"], azimuth, distance/1000.0)
        elevationAngle=atan(hill_local_xyz[3], distance)
#        @printf("              hill=%f+%f, dist=%f\n", hill_local_xyz[3], elevationDropCompensation(distance, earthRadius, vp.refractionCoef), distance)
#        @printf("              , pixel.x,y=%5.0f,%5.0f\n", (toRadians(azimuth)-vp.angleMin)/vp.angleStep , (vp.vertAngleMax-elevationAngle)/vp.angleStep )
        testX::UInt64 = round((toRadians(azimuth)-vp.angleMin)/vp.angleStep)
        testY::UInt64 = round((vp.vertAngleMax-elevationAngle)/vp.angleStep)
        visible::Bool = testPixel(distMap, testX, testY, 4, UInt16(trunc(distance/vp.distStep)), UInt16(5))
        @printf(", visible=%s\n", visible)
        if visible
            push!(dfFiltered, (hill["Summit"], hill["Elevation"], distance, testX, testY))
        end
    end
    println("Drawing ...")
    # Initialize
    Cairo_set_line_color(ctx::Cairo.CairoContext) = Cairo.set_source_rgb(ctx, 131/255, 148/255, 150/255)
    Cairo_set_text_color(ctx::Cairo.CairoContext) = Cairo.set_source_rgb(ctx,  38/255, 139/255, 210/255)
    function Cairo_line(ctx::Cairo.CairoContext, x1::Core.Real, y1::Core.Real, x2::Core.Real, y2::Core.Real)
        Cairo.move_to(ctx, x1, y1)
        Cairo.line_to(ctx, x2, y2)
        Cairo.stroke(ctx)
    end
    surf = Cairo.CairoARGBSurface(vp.outWidth, vp.outHeight)
    ctx  = Cairo.CairoContext(surf)
    # Background (previous image)
    bgSurf = Cairo.read_from_png("outlines.png")
    Cairo.set_source_surface(ctx, bgSurf, 0.0, 0.0)
    Cairo.paint(ctx)
    # Annotations
    Cairo.select_font_face(ctx, "Fira Sans", Cairo.FONT_SLANT_NORMAL, Cairo.FONT_WEIGHT_NORMAL)
    Cairo.set_font_size(ctx, 18.0)
    Cairo.set_line_width(ctx, 1.0)
    for poi in eachrow(dfFiltered)
        Cairo_set_line_color(ctx)
        Cairo_line(ctx, poi["X"]+0.5, poi["Y"], poi["X"]+0.5, 300.0)
        Cairo_set_text_color(ctx)
        Cairo.move_to(ctx, poi["X"]+5, 300.0-5.0)
        Cairo.save(ctx)
        Cairo.rotate(ctx, toRadians(-45.0))
        Cairo.show_text(ctx, poi["Summit"])
        Cairo.show_text(ctx, @sprintf(" (%3.0f km)", poi["Distance"]/1000.0))
        Cairo.restore(ctx)
    end
    # Azimuth ticks
    azMinD::Int = Int(ceil(toDegrees(vp.angleMin)))
    azMaxD::Int = Int(floor(toDegrees(vp.angleMax)))
    for az in azMinD:azMaxD
        x = round((toRadians(Float64(az))-vp.angleMin)/vp.angleStep)+0.5
        Cairo_set_line_color(ctx)
        Cairo_line(ctx, x, 38, x, 42)
        Cairo_line(ctx, x, 63, x, 68)
        Cairo_set_text_color(ctx)
        ext = Cairo.text_extents(ctx, @sprintf("%d", az))
        Cairo.move_to(ctx, x - ext[3]/2, 58)
        Cairo.show_text(ctx, @sprintf("%d °", az))
    end
    # Horizon line
    horizY = round(vp.vertAngleMax/vp.angleStep)+0.5
    Cairo.set_source_rgb(ctx, 238/255, 232/255, 213/255)
    Cairo_line(ctx, 0.0, horizY, vp.outWidth, horizY)
    # Finalize
    Cairo.write_to_png(surf, "outline-with-annotations.png" )
end

# Info (from https://www.udeuschle.de/panoramas/makepanoramas_en.htm)
# Lat: 50.08309 Lon 17.23094 Alt(auto+10m): 1500+10
# View direction: 112.5, extension 45 left: 90, right: 135, resolution 20pix/deg
# Tilt, range, vert. exaggeration 1.2

# TODO: determine lat/long range automatically
function main()
    tileDir     = "d:/_disk_d_old/devel-python/panorama/data_srtm"
	#tileDir     = "data_srtm"
    latLonRange = LatLonRange(47, 15, 50, 21)
    eye         = PositionLLH(50.08309, 17.23094, 1510)

    heightMap = loadData(latLonRange, tileDir)
    #saveHeightMap(data)
    ellipsoid = SphericalEarth()
    #ellipsoid = Wgs84()
    vp = ViewPort(ellipsoid, eye, toRadians(90.0), toRadians(135.0), -0.0560, 0.0339, 0.0001, 250.0e3, 1.18)
    distMap   = makeDistMap(vp, latLonRange, heightMap)

    minValue = minimum(distMap)
    maxValue = maximum(distMap)
    @printf("min=%d max=%d\n", minValue, maxValue)
    println("Saving distmap-gray.png")
    Images.save("distmap-gray.png", Images.Gray.(distMap/maxValue))

    println("Extracting outlines ...")
    outlines=extractOutlines(distMap)
    println("Saving outlines.png")
    Images.save("outlines.png", outlines)

    println("Creating annotations")
    drawSummits(vp, distMap)
    println("All done")

    # This can be fun: https://wiki.flightgear.org/Atmospheric_light_scattering
    # http://www.science-and-fiction.org/rendering/als.html
end

main()


using LinearAlgebra
using Plots

function main()

    # vector_ops()
    matrix_ops()

end

function vector_ops()
    x = [1, 2, 3]
    y = [1; 2; 3]
    z = [1 2 3]
    q = [1 2 3; 4 5 6]

    # display(x)
    # display(y)
    # display(z)
    # display(q)

    # norm
    # println(norm(x))
    # println(x / norm(x))
    # println(normalize(x))

    # dot product
    # println(dot(x, y))
    # println(x ⋅ y)

    # cross product
    println(cross(x,y))
    println(x × y)

end

function matrix_ops()

    A = [0 2 4; 2 4 2; 3 3 1]
    B = ones(3,3)
    C = [1 1; 1 1; 1 1]

    # matrix multiplication
    # display(A * B)
    # display(A * C)

    # elementwise multiplication
    # display(A .* B)

    # special operations
    # println(tr(A))
    # println(det(A))
    # display(transpose(A))
    # display(eigvals(A))
    # display(eigvecs(A))
    # display(inv(A))

    # special matrices
    # D = [1 0 0; 0 1 0; 0 0 1]
    # display(D)
    # D = Diagonal(D)

    # linear system Ax = b
    A = [1 2; 1 -1]
    b = [5, -1]

    # A = factorize(A)
    # display(A)
    # display(A.L * A.U)

    x0 = A \ b

    xlin = range(0, 5, length=50)
    f(x) = 5 - 2x
    g(x) = -1 + x

    println(x0)

    plot(xlin, f.(xlin), label="f(x)", xaxis="x", yaxis="y")
    plot!(xlin, g.(xlin), label="g(x)", title="Showing Solved Values")
    scatter!((x0[2], x0[1]), label="solved values", dpi=150)
end

main()


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:hydrogen
#     text_representation:
#       extension: .jl
#       format_name: hydrogen
#       format_version: '1.3'
#       jupytext_version: 1.10.3
#   kernelspec:
#     display_name: Julia 1.7.0
#     language: julia
#     name: julia-1.7
# ---

# %%
using Distributions
using StatsPlots

# %% [markdown]
# 負の二項分布の確率函数は
#
# $$
# P(k) = \binom{k+r-1}{k} p^r (1 - p)^k \quad (k = 0,1,2,\ldots)
# $$
#
# であり, $k$ は成功確率 $p$ のBernoulli試行で初めて $r$ 回成功するまでの失敗する回数を意味している.
#
# これのパラメータを $r = \alpha$, $p = 1/(N\theta)$ とおくと, $N$ が大きいとき $k/N$ は近似的に分布 $\operatorname{Gamma}(\alpha, \theta)$ に従う.

# %%
function plot_scaled_negbin(; α = 3, θ = 5, N = 100)
    gamma = Gamma(α, θ)
    negbin = LocationScale(0, 1/N, NegativeBinomial(α, 1/(N*θ)))

    xmax = quantile(gamma, 0.995)
    plot(; legend=:bottomright)
    plot!(x -> cdf(negbin, x), 0, xmax; label="NegativeBinomial(r = α, p = 1/(Nθ)) scaled by 1/N")
    plot!(x -> cdf(gamma, x), 0, xmax; label="Gamma(α = $α, θ = $θ)", ls=:dash)
    title!("cdf of scaled NegativeBinomial distribution for N = $N"; titlefontsize=10)
end

# %%
plot_scaled_negbin(N = 1)

# %%
plot_scaled_negbin(N = 10)

# %%
plot_scaled_negbin(N = 100)

# %%
α = 3
θ = 5
N = 100
gamma = Gamma(α, θ)
negbin = LocationScale(0, 1/N, NegativeBinomial(α, 1/(N*θ)))
X = rand(negbin, 10^5)

xmax = 60
stephist(X; norm=true, bin=0:xmax, label="NegativeBinomial(r = α, p = 1/(Nθ)) scaled by 1/N")
plot!(gamma, 0, xmax; label="Gamma(α = $α, θ = $θ)", ls=:dash)
title!("sample of scaled NegativeBinomial distribution for N = $N"; titlefontsize=10)

# %% [markdown]
# 二項分布の確率函数は
#
# $$
# P(k) = \binom{n}{k} p^k (1 - p)^{n-k} \quad (k = 0,1,\ldots,n)
# $$
#
# であり, $k$ は成功確率 $p$ の $n$ 回のBernoulli試行中の成功回数を意味している.
#
# これのパラメータを $p = \lambda/n$ とおくと, $n$ が大きいとき $k$ は近似的に分布 $\operatorname{Poisson}(\lambda)$ に従う.

# %%
function plot_bin(; λ = 5, n = 100)
    poisson = Poisson(λ)
    bin(n) = Binomial(n, λ/n)

    xmax = quantile(poisson, 0.995)
    plot(; legend=:bottomright)
    plot!(x -> cdf(bin(n), x), 0, xmax; label="Binomial(n, p = λ/n)")
    plot!(x -> cdf(poisson, x), 0, xmax; label="Poisson(λ = $λ)", ls=:dash)
    title!("cdf of Binomial distribution for n = $n"; titlefontsize=10)
end

# %%
plot_bin(n = 5)

# %%
plot_bin(n = 10)

# %%
plot_bin(n = 100)

# %%

0025/scaled NegativeBinomial distribution approximates Gamma distribution.jl


# indicator of a generic box

export IndBox, IndBallLinf

"""
**Indicator of a box**

    IndBox(low, up)

Returns the indicator function of the set
```math
S = \\{ x : low \\leq x \\leq up \\}.
```
Parameters `low` and `up` can be either scalars or arrays of the same dimension as the space: they must satisfy `low <= up`, and are allowed to take values `-Inf` and `+Inf` to indicate unbounded coordinates.
"""
struct IndBox{T <: Union{Real, AbstractArray}, S <: Union{Real, AbstractArray}} <: ProximableFunction
    lb::T
    ub::S
    function IndBox{T,S}(lb::T, ub::S) where {T <: Union{Real, AbstractArray}, S <: Union{Real, AbstractArray}}
        if !(eltype(lb) <: Real && eltype(ub) <: Real)
            error("`lb` and `ub` must be real")
        end
        if any(lb .> ub)
            error("`lb` and `ub` must satisfy `lb <= ub`")
        else
            new(lb, ub)
        end
    end
end

is_separable(f::IndBox) = true
is_convex(f::IndBox) = true
is_set(f::IndBox) = true
is_cone(f::IndBox) = all((f.lb .== -Inf) .+ (f.ub .== +Inf) .> 0)

IndBox(lb::T, ub::T) where {T <: Real} = IndBox{T, T}(lb, ub)

IndBox(lb::T, ub::S) where {T <: AbstractArray, S <: Real} = IndBox{T, S}(lb, ub)

IndBox(lb::T, ub::S) where {T <: Real, S <: AbstractArray} = IndBox{T, S}(lb, ub)

IndBox(lb::T, ub::S) where {T <: AbstractArray, S <: AbstractArray} =
    size(lb) != size(ub) ? error("bounds must have the same dimensions, or at least one of them be scalar") :
    IndBox{T, S}(lb, ub)

function (f::IndBox)(x::AbstractArray{R}) where R <: Real
    for k in eachindex(x)
        if x[k] < get_kth_elem(f.lb, k) || x[k] > get_kth_elem(f.ub, k)
            return +Inf
        end
    end
    return 0.0
end

function prox!(y::AbstractArray{R}, f::IndBox, x::AbstractArray{R}, gamma::Real=R(1)) where R <: Real
    for k in eachindex(x)
        if x[k] < get_kth_elem(f.lb, k)
            y[k] = get_kth_elem(f.lb, k)
        elseif x[k] > get_kth_elem(f.ub, k)
            y[k] = get_kth_elem(f.ub, k)
        else
            y[k] = x[k]
        end
    end
    return R(0)
end

prox!(y::AbstractArray{R}, f::IndBox, x::AbstractArray{R}, gamma::AbstractArray) where {R <: Real} = prox!(y, f, x, R(1))

"""
**Indicator of a ``L_∞`` norm ball**

    IndBallLinf(r=1.0)

Returns the indicator function of the set
```math
S = \\{ x : \\max (|x_i|) \\leq r \\}.
```
Parameter `r` must be positive.
"""
IndBallLinf(r::R=1.0) where {R <: Real} = IndBox(-r, r)

fun_name(f::IndBox) = "indicator of a box"
fun_dom(f::IndBox) = "AbstractArray{Real}"
fun_expr(f::IndBox) = "x ↦ 0 if all(lb ⩽ x ⩽ ub), +∞ otherwise"
fun_params(f::IndBox) =
    string( "lb = ", typeof(f.lb) <: AbstractArray ? string(typeof(f.lb), " of size ", size(f.lb)) : f.lb, ", ",
            "ub = ", typeof(f.ub) <: AbstractArray ? string(typeof(f.ub), " of size ", size(f.ub)) : f.ub)

function prox_naive(f::IndBox, x::AbstractArray{R}, gamma=R(1)) where R <: Real
    y = min.(f.ub, max.(f.lb, x))
    return y, R(0)
end


"""
# Usage

    solve(α, dx, dt, xStart, xEnd, n, κ, FiniteDiffIm())

Use implicit finite difference to discrete equations.

### References

```tex
@article{Murio2008ImplicitFD,
  title={Implicit finite difference approximation for time fractional diffusion equations},
  author={Diego A. Murio},
  journal={Comput. Math. Appl.},
  year={2008},
  volume={56},
  pages={1138-1145}
}
```

Matlab version: https://github.com/awstown/Fractional-Derivative
"""
struct FiniteDiffIm <: FractionalDiffEqAlgorithm end

function solve(α, dx, dt, xStart, xEnd, n, κ, u0t, uendt, u0, ::FiniteDiffIm)
    x = collect(0:dx:xEnd)
    t = collect(0:dt:n)

    U = zeros(Int64(n/dt + 1), Int64((xEnd - xStart)/dx + 1))

    # Boundry conditions
    U[:, 1] .= u0t
    U[:, end] .= uendt

    U[1, :] .= u0.(x)

    mu = (dt^α)/(dx^2)
    r = mu * gamma(2-α)
    A1 = diagm((1+2*r)*ones(length(x)-2))
    A2 = diagm(1 => -r*ones(length(x)-3))
    A3 = diagm(-1 => -r*ones(length(x)-3))
    A = A1 + A2 + A3

    U[2, 2:end-1] = A \ U[1, 2:end-1]

    j = collect(0:length(t))
    b_j = (j.+1).^(1-α) .- j.^(1-α)
    c_j = b_j[1:end-1] - b_j[2:end]

    V = copy(U[2:end, 2:end-1])
    for k = 1:length(V[:, 1])-1
        V[k+1, :] = A \ ImNextStep(V, U[1, 2:end-1], k, b_j, c_j)'
    end
    U[2:end, 2:end-1] = V
    return U
end

ImNextStep( V, U_0, k, b_j, c_j ) = c_j[1:k]' * reverse(V[1:k, :], dims=1) .+ (b_j[k+1].*U_0)'


using Stats
using Base.Test

@test_approx_eq mean([1, 2, 3]) 2.0
@test_approx_eq mean(1:3) 2.0

@test_approx_eq geomean([1, 2, 3]) (6.0)^(1/3)
@test_approx_eq geomean(1:3) (6.0)^(1/3)
@test_approx_eq geomean([2, 8]) 4.0
@test_approx_eq geomean([4, 1, 1/32]) 0.5
@test geomean([1, 0, 2]) == 0.0

@test_approx_eq harmmean([1, 2, 3]) 3 / (1 + 1/2 + 1/3)
@test_approx_eq harmmean(1:3) 3 / (1 + 1/2 + 1/3)
@test_approx_eq harmmean([1, 2, 4]) 12 / 7

@test_approx_eq trimmean([-100, 2, 3, 7, 200], 0.0) 22.4
@test_approx_eq trimmean([-100, 2, 3, 7, 200], 0.4) 4.0
@test_approx_eq trimmean([-100, 2, 3, 7, 200], 0.8) 3.0

@test_approx_eq wmean([1.0, 2.0, 3.0], [1/3, 1/3, 1/3]) 2.0
@test_approx_eq wmean([1.0, 2.0, 3.0], [1.0, 0.0, 0.0]) 1.0
@test_approx_eq wmean([1.0, 2.0, 3.0], [0.0, 1.0, 0.0]) 2.0
@test_approx_eq wmean([1.0, 2.0, 3.0], [0.0, 0.0, 1.0]) 3.0
@test_approx_eq wmean([1.0, 2.0, 3.0], [0.5, 0.0, 0.5]) 2.0
@test_approx_eq wmean([1.0, 2.0, 3.0], [0.5, 0.5, 0.0]) 1.5
@test_approx_eq wmean([1.0, 2.0, 3.0], [0.0, 0.5, 0.5]) 2.5

@test_approx_eq wmean(1:3, [1/3, 1/3, 1/3]) 2.0
@test_approx_eq wmean(1:3, [1.0, 0.0, 0.0]) 1.0
@test_approx_eq wmean(1:3, [0.0, 1.0, 0.0]) 2.0
@test_approx_eq wmean(1:3, [0.0, 0.0, 1.0]) 3.0
@test_approx_eq wmean(1:3, [0.5, 0.0, 0.5]) 2.0
@test_approx_eq wmean(1:3, [0.5, 0.5, 0.0]) 1.5
@test_approx_eq wmean(1:3, [0.0, 0.5, 0.5]) 2.5

@test_approx_eq mean(1:3, weights([1.0, 1.0, 0.5])) 1.8


################################################################################
#                                   SiO₂                                       #
################################################################################
export SiO₂

"""
This code creates a symbolic representation of the Sellmeier Equation model
for the index of refraction of amorphous SiO₂.Equation form is based on:
     Kitamura, et al.
     "Optical constants of silica glass from extreme ultraviolet to far
     infrared at near room temperature." Applied optics 46.33 (2007): 8118-8133.
which references
    Malitson, “Interspecimen comparison of the refractive index of fused
    silica,” J. Opt. Soc. Am.55,1205–1209 (1965)
and has been validated from 0.21-6.7 μm (free space wavelength).
The thermo-optic coefficient (for 300K) is from the literature, but I forgot
the source.
The symbolic index model and its derivatives in turn are used to generate
numerical functions for the SiO₂ index, group index and GVD as a function
of temperature and wavelength.
Variable units are lm in [um] and T in [deg C]
"""

p_n²_SiO₂ = (
    A₀ = 1,
    B₁ = 0.6961663,
    C₁ = (0.0684043)^2,     #                           [μm²]
    B₂ = 0.4079426,
    C₂ = (0.1162414)^2,     #                           [μm²]
    B₃ = 0.8974794,
    C₃ = (9.896161)^2,      #                           [μm²]
    dn_dT = 6.1e-6,         # thermo-optic coefficient  [K⁻¹]
    T₀ = 20,                # reference temperature     [°C]
)

function make_SiO₂(;p_n²=p_n²_SiO₂)
	@variables λ, T
	n = sqrt(n²_sym_fmt1( λ ; p_n²...)) + p_n².dn_dT  *  ( T - p_n².T₀  )
	n² = n^2
	ε 	= diagm([n², n², n²])
	ng = ng_model(n,λ)
	gvd = gvd_model(n,λ)
	models = Dict([
		:n		=>	n,
		:ng		=>	ng,
		:gvd	=>	gvd,
		:ε 		=> 	diagm([n², n², n²]),
	])
	defaults =	Dict([
		:λ		=>		0.8,		# μm
		:T		=>		p_n².T₀,	# °C

	])
	Material(models, defaults, :SiO₂, colorant"aqua")
end

################################################################

SiO₂ = make_SiO₂()
# SiO₂_num =

# struct NumMaterial{T,F1,F2,F3,TC} where {T, F1<:Function, F2<:Function, F3<:Function, TC}
# 	ε::T
# 	fε::F1
# 	fng::F2
# 	fgvd::F3
# 	fχ⁽²⁾::F4
# 	name::Symbol
# 	color::TC
# end


## Julia program for Random walk Metropolis-Hastings for Normal distribution
## author: weiya <szcfweiya@gmail.com>
## date: 2018-08-21

# random walk Metropolis-Hastings
function rmh(T, delta, f::Function, initval = 5)
    x = ones(T+1)
    x[1] = initval
    for t = 1:T
        # generate Yt
        epsi = rand() * 2 * delta - delta
        Yt = epsi + x[t]
        # accept or not
        u = rand()
        r = f(Yt)/f(x[t])
        if r >= 1
            x[t+1] = Yt
        else
            if u <= r
                x[t+1] = Yt
            else
                x[t+1] = x[t]
            end
        end
    end
    return(x)
end

# density function of N(0, 1) without normalization
function dnorm(x)
    return(exp(-0.5 * x^2))
end

# example
deltalist = [0.1, 0.5, 1]
N = 15000
# results of mean and variance
muvar = zeros(3, 2)
using Statistics
ps = []
for i = 1:3
    x = rmh(N, deltalist[i], dnorm)
    push!(ps, plot(x, legend=false))
    muvar[i, 1] = mean(x)
    muvar[i, 2] = var(x)
end
# plot
using Plots
plot(ps[1], ps[2], ps[3], layout=(3,1))
savefig("res_rmh.png")


# Generic update function

"""
    update(filter::AbstractFilter, b0::GaussianBelief, u::AbstractVector,
        y::AbstractVector)

Uses AbstractFilter filter to update gaussian belief b0, given control vector
u and measurement vector y.
"""
function update(filter::AbstractFilter, b0::GaussianBelief,
                u::AbstractVector{<:Number}, y::AbstractVector{<:Number})

    # predict
    bp = predict(filter, b0, u)

    # measure
    bn = measure(filter, bp, y; u = u)

    return bn
end

# Kalman filter functions

"""
    predict(filter::KalmanFilter, b0::GaussianBelief, u::AbstractVector)

Uses Kalman filter to run prediction step on gaussian belief b0, given control
vector u.
"""
function predict(filter::KalmanFilter, b0::GaussianBelief,
            u::AbstractVector{<:Number})

    # Motion update
    μp = filter.d.A * b0.μ + filter.d.B * u
    Σp = filter.d.A * b0.Σ * filter.d.A' + filter.d.W
    return GaussianBelief(μp, Σp)
end

"""
    measure(filter::KalmanFilter, bp::GaussianBelief, y::AbstractVector;
        u::AbstractVector = [false])

Uses Kalman filter to run measurement update on predicted gaussian belief bp,
given measurement vector y. If u is specified and filter.o.D has been declared,
then matrix D will be factored into the y predictions
"""
function measure(filter::KalmanFilter, bp::GaussianBelief, y::AbstractVector{<:Number};
                u::AbstractVector{<:Number} = [false])

    # Kalman Gain
    K = bp.Σ * filter.o.C' *
        inv(filter.o.C * bp.Σ * filter.o.C' + filter.o.V)

    # Predicted measurement
    yp = measure(filter.o, bp.μ, u)

    # Measurement update
    μn = bp.μ + K * (y-yp)
    Σn = (I - K * filter.o.C) * bp.Σ
    return GaussianBelief(μn, Σn)
end


#!/usr/bin/env julia

# Best run in the REPL until I work out how to get unicodeplots to print to stdout when in an `include`
# `env JULIA_NUM_THREADS=(nproc) julia --project`

using JustJoshing

import Plots
Plots.unicodeplots()

# This should look like Figure 5.3, page 121 in Joshi (it does)
# NB: looks like Joshi's graph is mislabelled - should be spot price
p = Plots.plot(); for t in 0:0.2499:1; Plots.plot!(p,x->C(x,t;K=100,σ=0.3,r=0),60:140); end; p

Plots.plot(x->C(1,x),0:0.001:0.9999) # Value of at-the-money approaches zero as time approaches maturity (Fig 2.1, page 35)

Plots.plot(x->C(0.5,0;σ=x),0:0.01:1) # Call options with volatile underlyings are more expensive (Fig 3.8, page 66)

# Simulated stock price
Plots.plot(x->B(x,σ=0.02),0:0.01:1)

# Monte-Carlo validation of contracts from payoffs

# max(S-K,0): call option
let S_t=100, t=0.01, K=100, r=0.02, σ=0.05, T=1, trials=100_000_000
    @show bs = C(S_t,t;T=T,K=K,r=r,σ=σ)

    @show mc = mc_pricer((S,K,t,T,r)->max(S-K,0),S_t;t=t,T=T,K=S_t,r=r,σ=σ) |> first

    @assert isapprox(mc, bs, rtol=1e-3)
end

# S-K: forward contract
let S_t=100, t=0.01, K=100, r=0.02, σ=0.05, T=1, trials=100_000_000
    @show exact = S_t - K*exp(-r*(T-t))

    @show mc = mc_pricer((S,K,t,T,r)->S-K,S_t;t=t,T=T,K=K,r=r,σ=σ) |> first

    @assert isapprox(mc, exact, rtol=1e-3)
end

# Int(S>E): binary option
let S_t=100, t=0.01, E=100, r=0.02, σ=0.05, T=1, trials=100_000_000
    @show exact = binary(S_t,t;E=E,r=r,σ=σ,T=T)

    @show mc = mc_pricer((S,E,t,T,r)->Int(S>E),S_t;t=t,T=T,K=E,r=r,σ=σ) |> first

    @assert isapprox(mc, exact, rtol=1e-3)
end

# Asian option, fixed strike
# TODO: compare prices with European options, look at impact volatility has
s = 80:1:120; t = [0; round.(-1*10 .^(-2:0.2:-1);sigdigits = 2)]
a = (x->mc_pricer_pathdep((S,K,t,T,r)->max(mean(S)-100,0),x[1],trials=100_000,r=0,t=x[2]:0.1:0,T=0,σ=0.5)).(Iterators.product(s,t))
plot(s,first.(a),labels=string.(t'))


@testset "dijkstra (dense)" begin
    begin
        g = AdjacencyMatrix(5)
        setedge!(g, 1, 2, 10)
        setedge!(g, 1, 3, 3)
        setedge!(g, 1, 4, 20)
        setedge!(g, 2, 4, 5)
        setedge!(g, 3, 2, 2)
        setedge!(g, 4, 5, 11)
        setedge!(g, 3, 5, 15)
    end
    @test dijkstra(g, 1) == [0, 5, 3, 10, 18]
end

@testset "dijkstra (sparse)" begin
    begin
        g = AdjacencyList(5)
        setedge!(g, 1, 2, 10)
        setedge!(g, 1, 3, 3)
        setedge!(g, 1, 4, 20)
        setedge!(g, 2, 4, 5)
        setedge!(g, 3, 2, 2)
        setedge!(g, 4, 5, 11)
        setedge!(g, 3, 5, 15)
    end
    @test dijkstra(g, 1) == [0, 5, 3, 10, 18]
end

@testset "jarnik (dense)" begin
    begin
        g = AdjacencyMatrix(6)
            setedge!(g, 1, 3, 7)
            setedge!(g, 3, 1, 7)
            setedge!(g, 1, 5, 9)
            setedge!(g, 5, 1, 9)
            setedge!(g, 3, 2, 5)
            setedge!(g, 2, 3, 5)
            setedge!(g, 3, 4, 1)
            setedge!(g, 4, 3, 1)
            setedge!(g, 3, 6, 2)
            setedge!(g, 6, 3, 2)
            setedge!(g, 2, 6, 6)
            setedge!(g, 6, 2, 6)
            setedge!(g, 4, 6, 2)
            setedge!(g, 6, 4, 2)
            setedge!(g, 5, 6, 1)
            setedge!(g, 6, 5, 1)
        result = AdjacencyMatrix(6)
            setedge!(result, 1, 3, 7)
            setedge!(result, 3, 1, 7)
            setedge!(result, 3, 2, 5)
            setedge!(result, 2, 3, 5)
            setedge!(result, 3, 4, 1)
            setedge!(result, 4, 3, 1)
            setedge!(result, 3, 6, 2)
            setedge!(result, 6, 3, 2)
            setedge!(result, 5, 6, 1)
            setedge!(result, 6, 5, 1)
    end
    @test jarnik(g, 1) == result
end


# Copyright 2017, Iain Dunning, Joey Huchette, Miles Lubin, and contributors    #src
# This Source Code Form is subject to the terms of the Mozilla Public License   #src
# v.2.0. If a copy of the MPL was not distributed with this file, You can       #src
# obtain one at https://mozilla.org/MPL/2.0/.                                   #src

# # The Rosenbrock function

# A nonlinear example of the classical Rosenbrock function.

using JuMP
import Ipopt
import Test

function example_rosenbrock()
    model = Model(Ipopt.Optimizer)
    set_silent(model)
    @variable(model, x)
    @variable(model, y)
    @NLobjective(model, Min, (1 - x)^2 + 100 * (y - x^2)^2)
    optimize!(model)

    Test.@test termination_status(model) == MOI.LOCALLY_SOLVED
    Test.@test primal_status(model) == MOI.FEASIBLE_POINT
    Test.@test objective_value(model) ≈ 0.0 atol = 1e-10
    Test.@test value(x) ≈ 1.0
    Test.@test value(y) ≈ 1.0
    return
end

example_rosenbrock()


using Revise
using DifferenceEquations
using Distributions
using Optim
using Random

function ar2_transition(u, p, t) # f
    A = [p[1] p[2]; 1 0]
    return A * u
end

function ar2_noise(u, p, t, noise::Nothing) # g
    z = [p[3] * randn(); 0]
    println(z)
    return [p[3] * randn(); 0]
end

function ar2_noise(u, p, t, noise) # g
    return [noise; 0]
end

function ar2_observation(u, p, t, noise) # h
    # @info "Observation" u
    return [1 0] * u
end

Random.seed!(1)

p = [0.5, -0.25]
tspan = (1, 15)

phi1 = 0.5
phi2 = -0.25
sigma = 5.0

Y = zeros(tspan[2])
n = sigma .* randn(length(Y))
Y += n

for t in 3:length(Y)
    Y[t] += phi1 * Y[t-1] + phi2 * Y[t-2]
end

u0 = [Y[2]; Y[1]]

# prob = StateSpaceProblem(
#     ar2_transition,
#     ar2_noise,
#     ar2_observation,
#     MvNormal(1,1),
#     [0.0, 0.0],
#     tspan,
#     nothing,
#     Y
# )

prob = StateSpaceProblem(
    ar2_transition,
    ar2_noise,
    ar2_observation,
    MvNormal(1,1),
    [0.0, 0.0],
    tspan,
    nothing, #n,
    Y
)

sol = DifferenceEquations._solve(prob, [phi1, phi2, sigma])
# target(x) = -DifferenceEquations._solve(prob, x).likelihood
# res = optimize(target, [0.0, 0.0, 1.0], LBFGS())


function Helmholtz{T<:AbstractFloat}(isz::Array{Int,1},isx::Array{Int,1},ot::Array{T,1},vp::Array{T,2},wav::Array{T,1},fmin::T,fmax::T,nf::Int,nt::Int,dz::T,dx::T,dt::T,ext::Int=10,atten_max::T=1.,flag=2)

# This function solves the acoustic Helmholtz equation for a prescribed
# source wavelet and range of frequencies. A complex-numbered attenuating
# boundary is used to damp the boundary reflections. This code is intended
# for simultaneous sources.
#
# INPUTS:     isz       - z-index for each source location
#             isx       - x-index for each source location
#             ot        - Activation time for each source
#             vp        - Acoustic velocity model
#             wav       - Source wavelet (time domain)
#             fmin      - Minimum frequency to model
#             fmax      - Maximum frequency to model
#             nf        - Number of frequency bins; 'nf' must be greater than or equal to 'length(wav)' and 'nt'
#             nt        - Number of time steps to perform modeling for
#             dz        - Grid spacing in z-direction
#             dx        - Grid spacing in x-direction
#             dt        - Sampling interval (time step)
#             ext       - Thickness (number of grid points) of absorbing boundary region
#             atten_max - Maximum complex amplitude in the attenuating boundary layer
#             flag      - Output frequency-domain data if flag==1, Output time-domain data if flag==2
#
# OUTPUTS:    u         - Solution to Helmhotz equation; stored as a 3D volume with dimensions (w,z,x) or (t,z,x)

    nz = size(vp,1) + 2*ext
    nx = size(vp,2) + 2*ext

    L = Laplacian(nz,nx,dz,dx)
    M = MassMatrix(vp,nz,nx,ext) .* Attenuation(nz,nx,ext,atten_max)

    WAV = fft([wav;zeros(nf-length(wav))])
    fs = 1/dt
    df = fs/nf
    faxis = fftshift(-fs/2:df:fs/2-df)
    waxis = 2*pi*faxis

    _,iwmin = findmin(abs(fmin-faxis))
    _,iwmax = findmin(abs(fmax-faxis))
    U = complex(zeros(nf,nz,nx))
    for iw = iwmin:iwmax
        w = waxis[iw]
        H = L + w^2*M
        s = Source(isz,isx,ot,WAV,waxis,w,nz,nx,ext)
        U_tmp = H\s
        U_tmp = reshape(U_tmp,nz,nx)
        U[iw,:,:] = U_tmp
        U[nf-iw+2,:,:] = conj(U_tmp)
    end

    if flag==1
        return U
    elseif flag==2
        return real(ifft(U,1))[1:nt,ext+1:end-ext,ext+1:end-ext]
    end

end

function Helmholtz{T<:AbstractFloat}(isz::Int,isx::Int,ot::T,vp::Array{T,2},wav::Array{T,1},fmin::T,fmax::T,nf::Int,nt::Int,dz::T,dx::T,dt::T,ext::Int=10,atten_max::T=1.;flag=2)

# This function solves the acoustic Helmholtz equation for a prescribed
# source wavelet and range of frequencies. A complex-numbered attenuating
# boundary is used to damp the boundary reflections. This code is intended
# for a single source.
#
# INPUTS:     isz       - z-index for source location
#             isx       - x-index for source location
#             ot        - Activation time for the source
#             vp        - Acoustic velocity model
#             wav       - Source wavelet (time domain)
#             fmin      - Minimum frequency to model
#             fmax      - Maximum frequency to model
#             nf        - Number of frequency bins; 'nf' must be greater than or equal to 'length(wav)' and 'nt'
#             nt        - Number of time steps to perform modeling for
#             dz        - Grid spacing in z-direction
#             dx        - Grid spacing in x-direction
#             dt        - Sampling interval (time step)
#             ext       - Thickness (number of grid points) of absorbing boundary region
#             atten_max - Maximum complex amplitude in the attenuating boundary layer
#             flag      - Output frequency-domain data if flag==1, Output time-domain data if flag==2
#
# OUTPUTS:    U         - Solution to Helmhotz equation; stored as a 3D volume with dimensions (w,z,x) or (t,z,x)
#
# Contributors: Landon Safron
# Institution: University of Alberta
# Date: March 1, 2017

    nz = size(vp,1) + 2*ext
    nx = size(vp,2) + 2*ext

    L = Laplacian(nz,nx,dz,dx)
    M = MassMatrix(vp,nz,nx,ext) .* Attenuation(nz,nx,ext,atten_max)

    WAV = fft([wav;zeros(nf-length(wav))])
    fs = 1/dt
    df = fs/nf
    faxis = fftshift(-fs/2:df:fs/2-df)
    waxis = 2*pi*faxis

    _,iwmin = findmin(abs(fmin-faxis))
    _,iwmax = findmin(abs(fmax-faxis))
    U = complex(zeros(nf,nz,nx))
    for iw = iwmin:iwmax
        w = waxis[iw]
        H = L + w^2*M
        s = Source(isz,isx,ot,WAV,waxis,w,nz,nx,ext)
        U_tmp = H\s
        U_tmp = reshape(U_tmp,nz,nx)
        U[iw,:,:] = U_tmp
        U[nf-iw+2,:,:] = conj(U_tmp)
    end

    if flag==1
        return U[:,ext+1:end-ext,ext+1:end-ext]
    elseif flag==2
        return real(ifft(U,1))[1:nt,ext+1:end-ext,ext+1:end-ext]
    end

end


# ======================================================================
# Logarithmic norms of matrices (a.k.a matrix measures)
#
# Reference:
#
# C. Desoer and M. Vidyasagar, Feedback Systems: Input-Output Properties.
# Philadelphia, PA: Society for Industrial and Applied Mathematics,
# Jan. 2009, ser. Classics in Applied Mathematics.
# ======================================================================

# logarithmic norm (also known as matrix measure) for commonly used p-norms
function logarithmic_norm(A::AbstractMatrix, p::Real=Inf)
    if p == Inf
        return logarithmic_norm_inf(A)
    elseif p == 1
        return logarithmic_norm_1(A)
    elseif p == 2
        return logarithmic_norm_2(A)
    else
        throw(ArgumentError("logarithmic norm only implemented for p = 1, 2, or Inf, got p = $p"))
    end
end

# max_j a_jj + ∑_{i ≠ j} |a_ij|
function logarithmic_norm_1(A::AbstractMatrix{N}) where {N}
    out = -Inf
    @inbounds for j in 1:size(A, 2)
        α = A[j, j]
        for i in 1:size(A, 1)
            if i ≠ j
                α += abs(A[i, j])
            end
        end
        if α > out
            out = α
        end
    end
    return out
end

# max_i a_ii + ∑_{j ≠ i} |a_ij|
function logarithmic_norm_inf(A::AbstractMatrix{N}) where {N}
    out = -Inf
    @inbounds for i in 1:size(A, 1)
        α = A[i, i]
        for j in 1:size(A, 2)
            if i ≠ j
                α += abs(A[i, j])
            end
        end
        if α > out
            out = α
        end
    end
    return out
end

# max_j  1/2 * λⱼ(A + A^T)
function logarithmic_norm_2(A::AbstractMatrix)
    B = A + A'
    λ = eigvals(B)
    return maximum(λ) / 2
end

# ========================================================================
# Specialized functions on LazySets.Arrays.SingleEntryVector
# TODO refactor to LazySets.jl
# ========================================================================

# difference between single entry vectors (SEV); it is type unstable because
# it may return either a SEV or a regular Vector depending on the indices
# but it is much faster than the fallback if the indices match
function minus(e1::SingleEntryVector{N}, e2::SingleEntryVector{N}) where {N}
    e1.n == e2.n || throw(DimensionMismatch("dimensions must match, but they are $(length(e1)) and $(length(e2)) respectively"))

    if e1.i == e2.i
        return SingleEntryVector(e1.i, e1.n, e1.v - e2.v)
    else
        out = zeros(N, e1.n)
        @inbounds begin
            out[e1.i] = e1.v
            out[e2.i] = -e2.v
        end
        return out
    end
end

# addition between single entry vectors (SEV); it is type unstable because
# it may return either a SEV or a regular Vector depending on the indices
# but it is much faster than the fallback if the indices match
function plus(e1::SingleEntryVector{N}, e2::SingleEntryVector{N}) where {N}
    e1.n == e2.n || throw(DimensionMismatch("dimensions must match, but they are $(length(e1)) and $(length(e2)) respectively"))

    if e1.i == e2.i
        return SingleEntryVector(e1.i, e1.n, e1.v + e2.v)
    else
        out = zeros(N, e1.n)
        @inbounds begin
            out[e1.i] = e1.v
            out[e2.i] = e2.v
        end
        return out
    end
end

# norm of the difference of two SEV ||x - y|| in the (vector p norm)
function normdiff(e1::SingleEntryVector{N}, e2::SingleEntryVector{N}, p::Real=2) where {N}
    e1.n == e2.n || throw(DimensionMismatch("dimensions must match, but they are $(length(e1)) and $(length(e2)) respectively"))

    if e1.i == e2.i
        δ = e1.v - e2.v
        return abs(δ)

    else
        a = abs(e1.v)
        b = abs(e2.v)
        if isinf(p)
            return max(a, b)
        else
            s = a^p + b^p
            return s^(1/p)
        end
    end
end

# fallback
function normdiff(x::AbstractVector{N}, y::AbstractVector{N}, p::Real=2) where {N}
    norm(x - y, p)
end


function unspliced(τ, u₀, α, β)
    expᵘ = exp(-β * τ)
    u₀ * expᵘ + α / β * (1 - expᵘ)
end

function spliced(τ, s₀, u₀, α, β, γ)
    c = (α - u₀ * β) / (γ - β)
    expᵘ = exp(-β * τ)
    expˢ = exp(-γ * τ)
    s₀ * expˢ + α / γ * (1 - expˢ) + c * (expˢ - expᵘ)
end

function mRNA(τ, u₀, s₀, α, β, γ)
    expᵘ, expˢ = exp(-β * τ), exp(-γ * τ)
    expᵘˢ = (α - u₀ * β) * inv(γ - β) * (expˢ - expᵘ)
    u = u₀ * expᵘ + α / β * (1 - expᵘ)
    s = s₀ * expˢ + α / γ * (1 - expˢ) + expᵘˢ
    u, s
end


using Plots, LaTeXStrings, Statistics

function Deposition(;len, tot_time, time_steps)
    Time = exp.(0:tot_time/(time_steps-1):tot_time)
    surf = [0 for i=1:len]
    VarList = [0.0 for i=1:time_steps]
    for n in 1:time_steps
        randsurf = rand(1:len,floor(Int,Time[n]*1000 + 1000))
        for i in randsurf
            surf[i] += 1
        end
        VarList[n] = std(surf)
    end
    return Time, VarList
end

function Linear_fit(;len, tot_time, time_steps)
    A = [hcat(log.(Time)) reshape(ones(time_steps), time_steps, 1)]
    b = reshape(log.(VarList), time_steps, 1)
    line = (A \ b)
    x = 0:tot_time
    y = x .* line[1] .+ line[2]
    return x, y, line
end

Parameters = Dict(
                :len => 200,
                :tot_time => 10,
                :time_steps => 20
                    )

Time, VarList = Deposition(;Parameters...)
X, Y, Line = Linear_fit(;Parameters...)

theme(:dark)
gr()

scatter(log.(Time),log.(VarList),
    xlabel= L"Log\ Time",
    ylabel= L"Log\ W_{(t)}",
    title= L"Log-Log\ Plot\ of\ ~W_{(t)}-Time~",
    label = L"Data\ point")
plot!(X,Y,label = L"y = %$(round(Line[1],digits= 2))x + %$(round(Line[2],digits= 2))")
savefig("C:\\Users\\Yaghoub\\Documents\\GitHub\\Ballistic-Deposition\\Deposition\\Random-Ballistic-Deposition\\Fig\\log-log.png")


module OrdinalRegr

using Convex, SCS

struct OrdinalSVM
    w
    b
    L
end

function fitordinalsvm(y, X, L; C=1e-2)
    n, p = size(X)
    l = length(L)
    w = Variable(p)
    b = Variable(l - 1)
    # get indexes of the labels
    inds = convert(Vector{Int}, indexin(y, L))
    # compute objective
    obj = C * sum(w .* w)
    f = X * w
    for (j, lj) in enumerate(L[1:end-1])
        for (i, yi) in enumerate(y)
            obj += yi ≤ lj ? max(0.0, 1.0 + (f[i] - b[j])) : max(0.0, 1.0 - (f[i] - b[j]))
        end
    end
    problem = minimize(obj, (b[i] <= b[i+1] for i in 1:l-2)...)
    solve!(problem, () -> SCS.Optimizer(verbose=false))
    return OrdinalSVM(w.value[:], b.value[:], L)
end

fitordinalsvm(y, X; C=1e-2) = fitordinalsvm(y, X, sort!(unique(y)); C=1e-2)

"""
    score(model::OrdinalSVM, X::AbstractMatrix)

Compute the scoring using an `OrdinalSVM` model.
"""
function score(model::OrdinalSVM, X::AbstractMatrix)
    X * model.w
end

"""
predict(model::OrdinalSVM, X::AbstractMatrix)

Predict the label using an `OrdinalSVM` model.
"""
function predict(model::OrdinalSVM, X::AbstractMatrix)
    l = [model.L[1] for i in 1:size(X, 1)]
    for (i, s) in enumerate(score(model, X))
        for (Lj, bj) in zip(model.L[2:end], model.b)
            s - bj ≥ 0 && (l[i] = Lj)
        end
    end
    return l

end

end


function get_dft_interval(max_val::Float64,N::Int64)
    #Returns the interval over which we obtain the Fourier transform
	(-N÷2:N-1-N÷2)*(2*max_val/N)
end

function get_reciprocal_dft_interval(max_val::Float64,N::Int64)
    #Returns the interval over which we have to sample our function to perform the DFT
	(-N÷2:N-1-N÷2)*(π/max_val)
end

function get_fourier_transform(k_max::Float64,N::Int64,ψ0)
	#Calculates the Fourier transform of the given initial profile ψ0

    ks = get_dft_interval(k_max,N)
    rs = get_reciprocal_dft_interval(k_max,N)

	#This is a matrix containing the ψ0s
	ψ0s = convert(Matrix{ComplexF64},[ ψ0(x,y) for x in rs, y in rs])

	#Then, we calculate the Fourier transform of ψ, which returns a matrix
	ϕs =  (0.5*π/k_max^2)*ifftshift( fft( fftshift(  ifftshift(fft(fftshift(ψ0s,1),1),1)  ,2),2),2)

	#Now, we interpolate this matrix, to be able to calculate the function at an arbitrary point.
	CubicSplineInterpolation( (ks , ks),ϕs,extrapolation_bc=0.0)
end

function display_fourier_transform(plot_range::Float64,plot_N::Int64,clim::Float64,ϕ)
	#Displays the fourier transform ϕ. It is useful to see if the chosen parameters are adequate
	ks = LinRange(-plot_range,plot_range,plot_N)

	I = [ real(ϕ(kx,ky)*conj(ϕ(kx,ky))) for ky in ks, kx in ks ]

	heatmap(ks,ks,I,clims=(0,clim),aspect_ratio=:equal,
	xlabel=L"k_x",ylabel=L"k_y",xlims=(-plot_range,plot_range),ylims=(-plot_range,plot_range),
	title="Fourier transform of initial profile")
end

function propagate_beam(r_max::Float64,N::Int64,z::Float64,ϕ)
	#Propagates a beam to a distance z given the fourier transform of its initial profile

	rs = get_dft_interval(r_max,N)
    ks = get_reciprocal_dft_interval(r_max,N)

	s = zeros(ComplexF64,N,N)

	Threads.@threads for n in 1:N
		Threads.@threads for m in 1:N
			s[n,m] = ϕ(ks[m],ks[n])*exp(-im*z*(ks[n]^2+ks[m]^2)/2)
		end
	end

	( 0.5*π/r_max^2 )*ifftshift( bfft( fftshift(  ifftshift(bfft(fftshift(s,1),1),1)  ,2),2),2)
end

function propagate_beam(r_max::Float64,N::Int64,z::Float64,ϕ,bplan1,bplan2)
	#Same as before, but accepts plans for the FFT.

	rs = get_dft_interval(r_max,N)
    ks = get_reciprocal_dft_interval(r_max,N)

	s = zeros(ComplexF64,N,N)

	Threads.@threads for n in 1:N
		Threads.@threads for m in 1:N
			s[n,m] = ϕ(ks[m],ks[n])*exp(-im*z*(ks[n]^2+ks[m]^2)/2)
		end
	end

	( 0.5*π/r_max^2 )*ifftshift(bplan2*fftshift(ifftshift(bplan1*fftshift(s,1),1),2),2)
end

function decrease_size(a,rs,I)
	#This function decreses the size of a range and corresponding intensities.
	#It is analogous to setting xlims=(-a,a), ylims=(-a,a) but somehow is faster
	K=1
	for k in eachindex(rs)
		if rs[k]>-a
			K = k
			break
		end
	end
	rs[ K:(length(rs)+1-K) ],I[K:(length(rs)+1-K),K:(length(rs)+1-K)]
end;

function display_beam(r_max::Float64,N::Int64,z::Float64,plot_range::Float64,clim::Float64,ϕ)
	#Displays a propageted beam
	rs = get_dft_interval(r_max,N)
	ψs = propagate_beam(r_max,N,z,ϕ)

	I = real(ψs.*conj(ψs))

	drs,dI = decrease_size(plot_range,rs,I)

	heatmap(drs,drs,dI,clims=(0,clim),aspect_ratio=:equal,title="z="*(@sprintf "%.2f" z),xlabel=L"x",ylabel=L"y",xlims=(-plot_range,plot_range),ylims=(-plot_range,plot_range))
end

function animate_beam(r_max::Float64,N::Int64,zmax::Float64,
	plot_range::Float64,clim::Float64,ϕ,fps::Int64,nframes::Int64,file_name::String)
	#Animates a beam propagation
	#The file_name should end in .gif

	rs = get_dft_interval(r_max,N)
	ks = get_reciprocal_dft_interval(r_max,N)

	plan1 = plan_bfft( zeros(ComplexF64, N,N ) , 1)
	plan2 = plan_bfft( zeros(ComplexF64, N,N ) , 2)

	anim = @animate for z in LinRange(0,zmax,nframes)
		ψs = propagate_beam(r_max,N,z,ϕ,plan1,plan2)

		I = real(ψs.*conj(ψs))

		drs,dI = decrease_size(plot_range,rs,I)

		heatmap(drs,drs,dI,clims=(0,clim),aspect_ratio=:equal,
		title=L"z="*(@sprintf "%.2f" z),xlabel=L"x",ylabel=L"y",
		xlims=(-plot_range,plot_range),size=(1200,900),labelfontsize=20,titlefontsize=20,tickfontsize=14)
	end

	gif(anim,file_name,fps=fps)
end;


# code for Video 4.2:
Pkg.add("Winston")
Pkg.add("Gadfly")
Pkg.add("PyPlot")
using Winston, Gadfly, PyPlot

fname = "iris.csv"
using DataFrames
data = readtable(fname, separator = ',')
hist(data[:sepal_length])  # (4.0:0.5:8.0,[5,27,27,30,31,18,6,6])

# drawing the histogram with Winston:
p = FramedPlot(title="Histogram sepal length", xlabel="Sepal Length", ylabel="Frequency")
add(p, Winston.Histogram(hist(data[:sepal_length])...))
display(p) # not necessary in the REPL
savefig(p, "Winston_Histogram.png")

# plotting with Gadfly:
using Gadfly
p = plot(data, x="sepal_length", y="petal_length", color="species", Geom.smooth,
    Guide.title("Lengths (\"length_width\")"))
draw(PDF("lengths.pdf", 14cm, 10cm), p)

# drawing with PyPlot:
using PyPlot
x = data[:sepal_width]
nbins = 50
ax = axes()
h = plt[:hist](x, nbins) # Histogram
PyPlot.grid("on")
PyPlot.xlabel("sepal width")
PyPlot.ylabel("frequency")
PyPlot.title("Histogram")


"""
	MMC(μ, λ, c)

Tạo mô hình M/M/c
"""
struct MMC{T} <: AbstractMMCK
	μ::Union{T, Real}
	λ::Union{T, Real}
	c::Union{T, Integer}
	r::Union{T, Real}
	ρ::Union{T, Real}
	function MMC(λ, μ, c)
		r = λ / μ
		ρ = r / c
		T = Union{typeof(μ), typeof(λ)}
		new{T}(μ, λ, c, r, ρ)
	end
end

function pn(m::MMC, n::Integer)
	μ = m.μ
	λ = m.λ
	c = m.c
	r = m.r
	ρ = m.ρ
	if n == 0
		d1 = (c * r^c) / factorial(c) / (c - r)
		d2 = sum(r^i / factorial(i) for i = 0:c-1)
		1 / (d1 + d2)
	elseif n < c
		pn(m, 0) * r^n / factorial(n)
	else pn(m, 0) * r^n / factorial(c) / c^(n-c)
	end
end

function Lq(m::MMC)
	r = m.r
	ρ = m.ρ
	c = m.c
	pn(m, 0) * r^c * ρ / factorial(m.c) / (1 - ρ)^2
end

function Wq(m::MMC)
	Lq(m) / m.λ
end

function W(m::MMC)
	Wq(m) + 1 / m.μ
end

function L(m::MMC)
	Lq(m) + m.r
end


function fit{C}(m::GaussianNB, X::Matrix{Float64}, y::Vector{C})
    ensure_data_size(X, y)
    # updatestats(m.dstats, X)
    # m.gaussian = MvNormal(mean(m.dstats), cov(m.dstats))
    # m.n_obs = m.dstats.n_obs
    n_vars = size(X, 1)
    for j=1:size(X, 2)
        c = y[j]
        m.c_counts[c] += 1
        updatestats(m.c_stats[c], reshape(X[:, j], n_vars, 1))
        # m.x_counts[c] .+= X[:, j]
        # m.x_totals += X[:, j]
        m.n_obs += 1
    end
    # precompute distributions for each class
    for c in keys(m.c_counts)
        m.gaussians[c] = MvNormal(mean(m.c_stats[c]), cov(m.c_stats[c]))
    end
    return m
end

"""Calculate log P(x|C)"""
function logprob_x_given_c{C}(m::GaussianNB, x::Vector{Float64}, c::C)
    return logpdf(m.gaussians[c], x)
end

"""Calculate log P(x|C)"""
function logprob_x_given_c{C}(m::GaussianNB, X::Matrix{Float64}, c::C)
    ## x_priors_for_c = m.x_counts[c] ./ m.x_totals
    ## x_probs_given_c = x_priors_for_c .^ x
    ## logprob = sum(log(x_probs_given_c))
    ## return logprob
    return logpdf(m.gaussians[c], X)
end


"""
    bubble_sorting(array::Array{Int64,1})

The Bubble sorting algorithm (BSA) is a primitive sorting algorithm that repeatedly steps
through the array by using a double for-loop with n and n-1 size. During the walkthrough,
the BSA compares adjacent elements and swaps wrong ordered elements until the array is
sorted. For more information see: [https://en.wikipedia.org/wiki/Bubble_sort](https://en.wikipedia.org/wiki/Bubble_sort)

# Arguments
- `array::Array{Int64,1}`: Unsorted array of integers

# Examples
```julia-repl
julia> import ClassicAlgorithmsCollections
julia> arr = [64, 34, 25, 12, 22, 11, 90]
julia> ClassicAlgorithmsCollections.bubble_sorting(arr)
[11, 12, 22, 25, 34, 64, 90]
```
"""
function bubble_sorting(array::Array{Int64,1})
    n = length(array)

    # Traverse through all array elements
    for i in 1:n
        # Last i elements are already in place
        for j in 1:n-i-1
            # traverse the array from 1 to n-i-1
            if array[j] > array[j+1]
                # Swap if the element found is greater than the next element
                array[j], array[j+1] = array[j+1], array[j]
            end
        end
    end
    return array
end