ArrayExt
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © wallneradam

//@version=5

// @description Array extensions: get/set, distance sorting, multi-array sorting
library("ArrayExt", overlay=true)


/////////////
// Get/Set //
/////////////

//
// Get elements from the array
//

// @function        Get element from the array at index, or na if index not found
// @param a         The array
// @param idx       The array index to get
// @returns         The array item if exists or na
export get(bool[] a, int idx) =>
    n = array.size(a)
    bool res = na
    if idx < n
        res := array.get(a, idx)
    res
    
// @function        Get element from the array at index, or na if index not found
// @param a         The array
// @param idx       The array index to get
// @returns         The array item if exists or na
export get(int[] a, int idx) =>
    n = array.size(a)
    int res = na
    if idx < n
        res := array.get(a, idx)
    res
    
// @function        Get element from the array at index, or na if index not found
// @param a         The array
// @param idx       The array index to get
// @returns         The array item if exists or na
export get(float[] a, int idx) =>
    n = array.size(a)
    float res = na
    if idx < n
        res := array.get(a, idx)
    res
    
// @function        Get element from the array at index, or na if index not found
// @param a         The array
// @param idx       The array index to get
// @returns         The array item if exists or na
export get(string[] a, int idx) =>
    n = array.size(a)
    string res = na
    if idx < n
        res := array.get(a, idx)
    res
    
// @function        Get element from the array at index, or na if index not found
// @param a         The array
// @param idx       The array index to get
// @returns         The array item if exists or na
export get(color[] a, int idx) =>
    n = array.size(a)
    color res = na
    if idx < n
        res := array.get(a, idx)
    res
    
// @function        Get element from the array at index, or na if index not found
// @param a         The array
// @param idx       The array index to get
// @returns         The array item if exists or na
export get(line[] a, int idx) =>
    n = array.size(a)
    line res = na
    if idx < n
        res := array.get(a, idx)
    res
    

//
// Set elements in the array
//

// @function        Set array item at index, if array has no index at the specified index, the array is filled with na
// @param a         The array
// @param idx       The array index to set
// @param val       The value to be set
export set(int[] a, int idx, simple int val) =>
    while idx >= array.size(a)
        array.push(a, na)
    array.set(a, idx, val)

// @function        Set array item at index, if array has no index at the specified index, the array is filled with na
// @param a         The array
// @param idx       The array index to set
// @param val       The value to be set
export set(float[] a, int idx, simple float val) =>
    while idx >= array.size(a)
        array.push(a, na)
    array.set(a, idx, val)
    
// @function        Set array item at index, if array has no index at the specified index, the array is filled with na
// @param a         The array
// @param idx       The array index to set
// @param val       The value to be set
export set(bool[] a, int idx, simple bool val) =>
    while idx >= array.size(a)
        array.push(a, na)
    array.set(a, idx, val)

// @function        Set array item at index, if array has no index at the specified index, the array is filled with na
// @param a         The array
// @param idx       The array index to set
// @param val       The value to be set
export set(string[] a, int idx, simple string val) =>
    while idx >= array.size(a)
        array.push(a, na)
    array.set(a, idx, val)

// @function        Set array item at index, if array has no index at the specified index, the array is filled with na
// @param a         The array
// @param idx       The array index to set
// @param val       The value to be set
export set(color[] a, int idx, simple color val) =>
    while idx >= array.size(a)
        array.push(a, na)
    array.set(a, idx, val)

// @function        Set array item at index, if array has no index at the specified index, the array is filled with na
// @param a         The array
// @param idx       The array index to set
// @param val       The value to be set
export set(line[] a, int idx, simple line val) =>
    while idx >= array.size(a)
        array.push(a, na)
    array.set(a, idx, val)
    
    
//////////////
// Sortting //
//////////////

//
// Distance sorting
//

// @function            Sort arrays by distance from a source (Inner helper function which works without types)
// @param array         The array to be sorted
// @param dist_from     The sorting is based on the distance of this source
// @param reverse       Reversed order if true
// @returns             Array of old indexes
_dist_sort(array, float dist_from, bool reverse = false) =>
    n = array.size(array)
    index_array = array.new_int(n)
    if n > 0
        for i = 0 to n - 1
            array.set(index_array, i, i)
        n -= 1
        for i = 0 to n > 0 ? n - 1 : na
            for j = 0 to n - i - 1
                t = array.get(array, j) 
                t1 = array.get(array, j + 1) 
                    
                if (not reverse and math.abs(t - dist_from) > math.abs(t1 - dist_from)) or (reverse and math.abs(t - dist_from) < math.abs(t1 - dist_from))
                    array.set(array, j, t1)
                    array.set(array, j + 1, t)
                    
                    // Sort indexes of the original order
                    _ti = array.get(index_array, j)
                    array.set(index_array, j, array.get(index_array, j + 1))
                    array.set(index_array, j + 1, _ti)
    index_array
    
// @function            Sort arrays by distance from a source
// @param array         The array to be sorted
// @param dist_from     The sorting is based on the distance of this source
// @param reverse       Reversed order if true
// @returns             Array of old indexes
dist_sort(int[] array, float dist_from, bool reverse) => _dist_sort(array, dist_from, reverse)
// @function            Sort arrays by distance from a source
// @param array         The array to be sorted
// @param dist_from     The sorting is based on the distance of this source
// @param reverse       Reversed order if true
// @returns             Array of old indexes
dist_sort(float[] array, float dist_from, bool reverse) => _dist_sort(array, dist_from, reverse)
// @function            Sort arrays by distance from a source
// @param array         The array to be sorted
// @param dist_from     The sorting is based on the distance of this source
// @param reverse       Reversed order if true
// @returns             Array of old indexes


//
// Multi sort
//

// @function            Sort arrays together (Inner helper function which works without types)
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param dist_from     If specified, the sorting is based on the distance of the specified source
// @param reverse       Reversed order if true
// @returns             Array of old indexes
_multisort(base_array, second_array, float dist_from = na, bool reverse = false) =>
    n = array.size(base_array)
    index_array = array.new_int(n)
    if n > 0
        for i = 0 to n - 1
            array.set(index_array, i, i)
        n -= 1
        for i = 0 to n > 0 ? n - 1 : na
            for j = 0 to n - i - 1
                t = array.get(base_array, j) 
                t1 = array.get(base_array, j + 1) 
                    
                if (na(dist_from) and (not reverse ? t > t1 : t < t1)) or (not na(dist_from) and (not reverse ? math.abs(t - dist_from) > math.abs(t1 - dist_from) : math.abs(t - dist_from) < math.abs(t1 - dist_from)))
                    array.set(base_array, j, t1)
                    array.set(base_array, j + 1, t)
                    
                    _t = array.get(second_array, j)
                    array.set(second_array, j, array.get(second_array, j + 1))
                    array.set(second_array, j + 1, _t)
                    
                    // Sort indexes of the original order
                    _ti = array.get(index_array, j)
                    array.set(index_array, j, array.get(index_array, j + 1))
                    array.set(index_array, j + 1, _ti)
    index_array

// The same for strings    
_str_multisort(string[] base_array, second_array, bool reverse = false) =>
    n = array.size(base_array)
    index_array = array.new_int(n)
    if n > 0
        for i = 0 to n - 1
            array.set(index_array, i, i)
        n -= 1
        for i = 0 to n > 0 ? n - 1 : na
            for j = 0 to n - i - 1
                t = array.get(base_array, j) 
                t1 = array.get(base_array, j + 1) 
                ta = array.from(t, t1)
                array.sort(ta, not reverse ? order.ascending: order.descending)
                if t1 == array.get(ta, 0)
                    array.set(base_array, j, t1)
                    array.set(base_array, j + 1, t)
                    _t = array.get(second_array, j)
                    array.set(second_array, j, array.get(second_array, j + 1))
                    array.set(second_array, j + 1, _t)
                    // Sort indexes of the original order
                    _ti = array.get(index_array, j)
                    array.set(index_array, j, array.get(index_array, j + 1))
                    array.set(index_array, j + 1, _ti)
    index_array
    

// @function            Reorder array based on indexes array (Inner helper function which works without types)
// @param index_array   The array of indexes of the new order
// @param array         The array to be reorder
_reorder(index_array, array) =>
    n = array.size(index_array)
    ta = array.copy(array)
    if n > 0
        for i = 0 to n > 0 ? n - 1 : na
            idx = array.get(index_array, i)
            array.set(array, i, array.get(ta, idx))
    
            
            
// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param dist_from     If specified, the sorting is based on the distance of the specified source like 'close'
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(int[] base_array, int[] second_array, float dist_from = na, bool reverse = false) => _multisort(base_array, second_array, dist_from, reverse)
// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param dist_from     If specified, the sorting is based on the distance of the specified source like 'close'
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(int[] base_array, float[] second_array, float dist_from = na, bool reverse = false) => _multisort(base_array, second_array, dist_from, reverse)
// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param dist_from     If specified, the sorting is based on the distance of the specified source like 'close'
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(int[] base_array, bool[] second_array, float dist_from = na, bool reverse = false) => _multisort(base_array, second_array, dist_from, reverse)
// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param dist_from     If specified, the sorting is based on the distance of the specified source like 'close'
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(int[] base_array, string[] second_array, float dist_from = na, bool reverse = false) => _multisort(base_array, second_array, dist_from, reverse)
// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param dist_from     If specified, the sorting is based on the distance of the specified source like 'close'
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(int[] base_array, color[] second_array, float dist_from = na, bool reverse = false) => _multisort(base_array, second_array, dist_from, reverse)
// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param dist_from     If specified, the sorting is based on the distance of the specified source like 'close'
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(int[] base_array, line[] second_array, float dist_from = na, bool reverse = false) => _multisort(base_array, second_array, dist_from, reverse)

// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param dist_from     If specified, the sorting is based on the distance of the specified source like 'close'
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(float[] base_array, int[] second_array, float dist_from = na, bool reverse = false) => _multisort(base_array, second_array, dist_from, reverse)
// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param dist_from     If specified, the sorting is based on the distance of the specified source like 'close'
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(float[] base_array, float[] second_array, float dist_from = na, bool reverse = false) => _multisort(base_array, second_array, dist_from, reverse)
// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param dist_from     If specified, the sorting is based on the distance of the specified source like 'close'
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(float[] base_array, bool[] second_array, float dist_from = na, bool reverse = false) => _multisort(base_array, second_array, dist_from, reverse)
// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param dist_from     If specified, the sorting is based on the distance of the specified source like 'close'
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(float[] base_array, string[] second_array, float dist_from = na, bool reverse = false) => _multisort(base_array, second_array, dist_from, reverse)
// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param dist_from     If specified, the sorting is based on the distance of the specified source like 'close'
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(float[] base_array, color[] second_array, float dist_from = na, bool reverse = false) => _multisort(base_array, second_array, dist_from, reverse)
// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param dist_from     If specified, the sorting is based on the distance of the specified source like 'close'
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(float[] base_array, line[] second_array, float dist_from = na, bool reverse = false) => _multisort(base_array, second_array, dist_from, reverse)

// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(string[] base_array, int[] second_array, bool reverse = false) => _str_multisort(base_array, second_array, reverse)
// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(string[] base_array, float[] second_array, bool reverse = false) => _str_multisort(base_array, second_array, reverse)
// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(string[] base_array, bool[] second_array, bool reverse = false) => _str_multisort(base_array, second_array, reverse)
// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(string[] base_array, string[] second_array, bool reverse = false) => _str_multisort(base_array, second_array, reverse)
// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(string[] base_array, color[] second_array, bool reverse = false) => _str_multisort(base_array, second_array, reverse)
// @function            Sort arrays together
// @param base_array    The base array to sort
// @param second array  The second array elements swaped the same way as base array
// @param reverse       Reversed order if true
// @returns             Array of old indexes
export multisort(string[] base_array, line[] second_array, bool reverse = false) => _str_multisort(base_array, second_array, reverse)

// @function            Reorder array based on indexes
// @param index_array   The array of indexes of the new order
// @param array         The array to be reorder
export reorder(int[] indexes, int[] array) => _reorder(indexes, array)
// @function            Reorder array based on indexes
// @param index_array   The array of indexes of the new order
// @param array         The array to be reorder
export reorder(int[] indexes, float[] array) => _reorder(indexes, array)
// @function            Reorder array based on indexes
// @param index_array   The array of indexes of the new order
// @param array         The array to be reorder
export reorder(int[] indexes, bool[] array) => _reorder(indexes, array)
// @function            Reorder array based on indexes
// @param index_array   The array of indexes of the new order
// @param array         The array to be reorder
export reorder(int[] indexes, string[] array) => _reorder(indexes, array)
// @function            Reorder array based on indexes
// @param index_array   The array of indexes of the new order
// @param array         The array to be reorder
export reorder(int[] indexes, color[] array) => _reorder(indexes, array)
// @function            Reorder array based on indexes
// @param index_array   The array of indexes of the new order
// @param array         The array to be reorder
export reorder(int[] indexes, line[] array) => _reorder(indexes, array)


//////////
// Test //
//////////

//
// Sort number arrays
//
a1 = array.from(11, 5, 36, 0.1, 12.3, 44)
a2 = array.from(0, 1, 2, 3, 4, 5)
a3 = array.from(10, 11, 12, 13, 14, 15)

// Sorting 2 arrays
idxs = multisort(a1, a2, reverse=true)
// Sort more arrays if necessary
reorder(idxs, a3)

//
// Sort strings
//
a11 = array.from("c", "b", "a", "w", "arr", "sort")
a12 = array.from(0, 1, 2, 3, 4, 5)

multisort(a11, a12)

//
// Miltisort by distance
//
a21 = array.from(close[1], close[2], close[3], close[4], close[5], close[6])
a22 = array.from(0, 1, 2, 3, 4, 5)
multisort(a21, a22, close)


//
// Print data to data window
//
plotchar(get(a1, 0), "a1[0]", "", location.top, size=size.tiny, color=color.gray)
plotchar(get(a1, 1), "a1[1]", "", location.top, size=size.tiny, color=color.gray)
plotchar(get(a1, 2), "a1[2]", "", location.top, size=size.tiny, color=color.gray)
plotchar(get(a1, 3), "a1[3]", "", location.top, size=size.tiny, color=color.gray)
plotchar(get(a1, 4), "a1[4]", "", location.top, size=size.tiny, color=color.gray)
plotchar(get(a1, 5), "a1[5]", "", location.top, size=size.tiny, color=color.gray)

plotchar(get(a2, 0), "a2[0]", "", location.top, size=size.tiny, color=color.blue)
plotchar(get(a2, 1), "a2[1]", "", location.top, size=size.tiny, color=color.blue)
plotchar(get(a2, 2), "a2[2]", "", location.top, size=size.tiny, color=color.blue)
plotchar(get(a2, 3), "a2[3]", "", location.top, size=size.tiny, color=color.blue)
plotchar(get(a2, 4), "a2[4]", "", location.top, size=size.tiny, color=color.blue)
plotchar(get(a2, 5), "a2[5]", "", location.top, size=size.tiny, color=color.blue)

plotchar(get(a3, 0), "a3[0]", "", location.top, size=size.tiny, color=color.orange)
plotchar(get(a3, 1), "a3[1]", "", location.top, size=size.tiny, color=color.orange)
plotchar(get(a3, 2), "a3[2]", "", location.top, size=size.tiny, color=color.orange)
plotchar(get(a3, 3), "a3[3]", "", location.top, size=size.tiny, color=color.orange)
plotchar(get(a3, 4), "a3[4]", "", location.top, size=size.tiny, color=color.orange)
plotchar(get(a3, 5), "a3[5]", "", location.top, size=size.tiny, color=color.orange)

plotchar(get(a12, 0), "a12[0]", "", location.top, size=size.tiny, color=color.green)
plotchar(get(a12, 1), "a12[1]", "", location.top, size=size.tiny, color=color.green)
plotchar(get(a12, 2), "a12[2]", "", location.top, size=size.tiny, color=color.green)
plotchar(get(a12, 3), "a12[3]", "", location.top, size=size.tiny, color=color.green)
plotchar(get(a12, 4), "a12[4]", "", location.top, size=size.tiny, color=color.green)
plotchar(get(a12, 5), "a12[5]", "", location.top, size=size.tiny, color=color.green)

plotchar(get(a22, 0), "a22[0]", "", location.top, size=size.tiny, color=color.red)
plotchar(get(a22, 1), "a22[1]", "", location.top, size=size.tiny, color=color.red)
plotchar(get(a22, 2), "a22[2]", "", location.top, size=size.tiny, color=color.red)
plotchar(get(a22, 3), "a22[3]", "", location.top, size=size.tiny, color=color.red)
plotchar(get(a22, 4), "a22[4]", "", location.top, size=size.tiny, color=color.red)
plotchar(get(a22, 5), "a22[5]", "", location.top, size=size.tiny, color=color.red)
