# Methods to provide grids and distance-matrices for
# different topologies of Kohonen maps (SOMs).


"""
    gridRectangular(xdim, ydim)

Create coordinates of all neurons on a rectangular SOM.

The return-value is an array of size (Number-of-neurons, 2) with
x- and y- coordinates of the neurons in the first and second
column respectively.
The distance between neighbours is 1.0.
The point of origin is bottom-left.
The first neuron sits at (0,0).
"""
function gridRectangular(xdim, ydim)

    grid = zeros(Float64, (xdim*ydim, 2))
    for ix in 1:xdim
        for iy in 1:ydim

            grid[ix+(iy-1)*xdim, 1] = ix-1
            grid[ix+(iy-1)*xdim, 2] = iy-1
        end
    end
    return grid
end



"""
    gridHexagonal(xdim, ydim)

Create coordinates of all neurons on a hexagonal SOM.

The return-value is an array of size (Number-of-neurons, 2) with
x- and y- coordinates of the neurons in the first and second
column respectively.
The distance between neighbours is 1.0.
The point of origin is bottom-left.
The first neuron sits at (0,0).
"""
function gridHexagonal(xdim, ydim)

    grid = zeros(Float64, (xdim*ydim, 2))
    rowHeight = √(1-0.25)

    for ix in 1:xdim
        for iy in 1:ydim

            if iy % 2 != 0    # 1st, 3rd, ... line
                grid[ix+(iy-1)*xdim, 1] = ix - 1
            else                    # 2nd, 4th, ... line
                grid[ix+(iy-1)*xdim, 1] = ix - 0.5
            end

            grid[ix+(iy-1)*xdim, 2] = (iy-1) * rowHeight
        end
    end
    return grid
end


"""
    gridSpherical(numNeurons)

Create coordinates of dim neurons on a spherical SOM.

The neurons are almost equally distributed on a sphere by
applying Fibonacci's spirals.
The radius of the sphere is adapted to get a distance of neighbours
of about 1.0.
The return-value is an array of size (Number-of-neurons, 3) with
x-, y- and z- coordinates of the neurons in the first, second and third
column respectively.
The point of origin is bottom-left.
The center of the sphere sits at (0,0).
"""
function gridSpherical(numNeurons)

    r = √(numNeurons/(4 * π))
    grid = zeros(Float64, (numNeurons, 3))

    # iterate Fibonacci spiral:
    for i in 1:(numNeurons)
        py = (i-1) * 2/numNeurons - 1 + (1/numNeurons)
        pr = √(1-py^2)
        ϕ  = (i-1) * π * (3-√5)
        px = cos(ϕ) * pr
        pz = sin(ϕ) * pr

        #println("raw: $px $py $pz")

        # shift point from origin to atom location:
        px = r * px
        py = r * py
        pz = r * pz

        #println("scaled: $px $py $pz")
        grid[i,:] = [px, py, pz]
    end
    return grid
end

# verify sphere:
# x = gridSpherical(50)
# R"""
# library(rgl)
# plot3d($x)
# #readline(prompt="Press [enter] to continue")
# """


"""
    distMatrix(grid::Array, toroidal::Bool)

Return the distance matrix for a non-toroidal or toroidal SOM.

# Arguments
- `grid`: coordinates of all neurons as generated by
          one of the `grid-`functions with x-coordinates in 1st column
          and y-coordinates in 2nd column.
- `toroidal`: true for a toroidal SOM.
"""
function distMatrix(grid::Array, toroidal::Bool)

    X = 1
    Y = 2
    xdim = maximum(grid[:,X]) - minimum(grid[:,X]) + 1.0
    ydim = maximum(grid[:,Y]) - minimum(grid[:,Y]) + 1.0

    numNeurons = nrow(grid)

    dm = zeros(Float64, (numNeurons,numNeurons))
    for i in 1:numNeurons
        for j in 1:numNeurons
            Δx = abs(grid[i,X] - grid[j,X])
            Δy = abs(grid[i,Y] - grid[j,Y])

            if toroidal
                Δx = min(Δx, xdim-Δx)
                Δy = min(Δy, ydim-Δy)
            end

            dm[i,j] = √(Δx^2 + Δy^2)
        end
    end
    # show(STDOUT, "text/plain",  grid)
    # show(STDOUT, "text/plain",  dm)
    return dm
end



"""
    distMatrixSphere(grid::Array)

Return the distance matrix for a spherical SOM.

# Arguments
- `grid`: coordinates of all neurons as gereated by
          one of the `grid-`functions with x-coordinates in 1st,
          y-coordinates in 2nd and z-coordinates in 3rd column.
          Point of origin must be in the center of the spere.
"""
function distMatrixSphere(grid::Array)

    r = (maximum(grid) - minimum(grid)) / 2

    numNeurons = nrow(grid)

    dm = zeros(Float64, (numNeurons,numNeurons))
    for i in 1:numNeurons
        for j in 1:numNeurons
            len_i = norm(grid[i,:])
            len_j = norm(grid[j,:])
            bog = grid[i,:] ⋅ grid[j,:] / (len_i * len_j)
            if bog > 1
                bog = 1.0
            end
            α = acos(bog)
            dm[i,j] = r * α
        end
    end
    return dm
end
