Zigzag Array Experimental
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © RozaniGhani-RG

//@version=5
indicator('Zigzag Array Experimental', 'ZAE', true)

// 0. Inputs
// 1. Types
// 2. Switches
// 3. Variables and arrays
// 4. Custom Functions
// 5. Execution
// 6. Constructs

//#region ———————————————————— 0. Inputs
T0 = 'Zigzag values\nDefault : 14\nMin : 2\nMax : 50'
T1 = 'Bar values\nDefault : 10\nMin : 0\nMax : 20'
T2 = 'Small font size recommended for mobile app or multiple layout'
T3 = 'Default\nStyle : Solid\nWidth : 4'
length    = input.int(            14,  'Length', inline = '0', minval = 2, maxval = 50, tooltip = T0)
bar       = input.int(            10,     'Bar', minval =  0,  maxval = 20, tooltip = T1)
colorUp   = input.color(color.lime,   'Trend', inline = '0')
colorDn   = input.color( color.red,        '', inline = '0')
showLabel = input.bool(         true,   'Label', group = 'Show / hide', inline = '1')
showLine  = input.bool(         true,    'Line', group = 'Show / hide', inline = '1')
showTable = input.string(    'Label', '| Table', group = 'Show / hide', inline = '1', options = ['Label', 'Line', 'None'])
fontSize  = input.string(   'normal',    'Size', group = 'Label', inline = '4', options = ['tiny',  'small', 'normal', 'large', 'huge'], tooltip = T2)
lineType  = input.string(    'solid', 'Display', group =  'Line', inline = '5', options = ['dash', 'dot', 'solid', 'arrow right', 'arrow left'])
width     = input.int(             4,        '', group =  'Line', inline = '5', minval = 1, maxval = 4, tooltip = T3)
//#endregion

//#region ———————————————————— 1. Types
// @type        Used for point especially for array
// @field x     int value for bar_index   
// @field y     float value for price
// @field sty   label style
// @field col   color for text label
// @field str   high or low string
type point
    int          x = na
    float        y = na
    string     sty = na
    color      col = na
    string     str = na

// @type          Used for initial setup
// @field hi      high value
// @field lo      low value
// @field colorHi color for high value
// @field colorLo color for low value
// @field strHi   string for high value
// @field strLo   string for low value
type startUp
    float       hi = na
    float       lo = na
    color  colorHi = na
    color  colorLo = na
    string   strHi = na
    string   strLo = na
//#endregion

//#region ———————————————————— 2. Switches
switchLine        = switch lineType
    'dash'        => line.style_dashed
    'dot'         => line.style_dotted
    'solid'       => line.style_solid
    'arrow right' => line.style_arrow_right
    'arrow left'  => line.style_arrow_left
//#endregion

//#region ———————————————————— 3. Variables and arrays
float ph   = na,  ph := ta.highestbars(high, length) == 0 ? high : na
float pl   = na,  pl := ta.lowestbars(  low, length) == 0 ?  low : na  
var  dir   =  0, dir := ph and na(pl) ? 1 : pl and na(ph) ? -1 : dir
var zigzag = array.new<point>(0)
oldzigzag  = zigzag.copy()
dirchanged = ta.change(dir)
varSetup   = startUp.new(ph, pl, colorUp, colorDn, 'H', 'L')
//#endregion

//#region ———————————————————— 4. Custom Functions
// @function         variable for point
// @param   setup    type containing ternary conditional operator
// @returns newPoint to be used later in initialize
method dirVariables(startUp setup = na) =>
    var point newPoint = na 
    x   = bar_index
    y   = dir == 1 ?               setup.hi : setup.lo
    sty = dir == 1 ? label.style_label_down : label.style_label_up
    col = dir == 1 ?          setup.colorHi : setup.colorLo
    str = dir == 1 ?            setup.strHi : setup.strLo  
    newPoint  := point.new(x, y, sty, col, str)
    newPoint

// @function         initialize zigzag array
// @param   setup    type containing ternary conditional operator
// @param   maxSize  maximum array size
// @returns zigzag   array after cleanup
method initialize(point[] zigzag = na, startUp setup = na, int maxSize = 10)=>
    newPoint = setup.dirVariables()
    zigzag.unshift(newPoint)
    if zigzag.size() > maxSize
        zigzag.pop()

// @function         update zigzag array
// @param   setup    type containing ternary conditional operator
// @param   maxSize  maximum array size
// @param   dir      direction value
// @returns zigzag   array after cleanup   
method update(point[] zigzag = na, startUp setup = na, maxSize = 10, int dir = na)=>
    if array.size(zigzag) == 0
        zigzag.initialize(setup, maxSize)
    else
        newPoint = setup.dirVariables()
        dirOver = dir ==  1 and newPoint.y > zigzag.get(0).y
        dirLess = dir == -1 and newPoint.y < zigzag.get(0).y
        if dirOver or dirLess
            zigzag.set(0, newPoint)
        point.new(na, na, na, na, na)

// @function          compare zigzag
// @param   zigzag    original array
// @param   oldzigzag copied array
// @returns boolOr    Or statement
// @returns boolAnd   And statement  
method boolPoint(point[] zigzag = na, point[] oldzigzag = na, int offset = 0) =>
    boolOr = zigzag.get(offset + 0).x != oldzigzag.get(offset + 0).x  or zigzag.get(offset + 0).y != oldzigzag.get(offset + 0).y
    boolAnd = zigzag.get(offset + 1).x == oldzigzag.get(offset + 1).x and zigzag.get(offset + 1).y == oldzigzag.get(offset + 1).y
    [boolOr, boolAnd]

// @function         create label based on zigzag array
// @param   zigzag   original array
// @param   size     font size
// @param   offset   default value zero
// @returns          new label
method createLabel(point[] zigzag = na, string size = na, int offset = 0, bool enable = true, color col = chart.fg_color) =>
    compare = dir ==   1 ? zigzag.get(offset + 0).y > zigzag.get(offset + 2).y ? 'H' : 'L' :
                 zigzag.get(offset + 0).y < zigzag.get(offset + 2).y ? 'L' : 'H'
    draw = label.new(
                 x         =       int(zigzag.get(offset + 0).x),
                 y         =           zigzag.get(offset + 0).y,
                 text      = compare + zigzag.get(offset + 0).str,
                 xloc      =               xloc.bar_index,
                 color     = color.new(color.blue, 100),
                 style     =           zigzag.get(offset + 0).sty,
                 textcolor =  enable ? zigzag.get(offset + 0).col : col,
                 size      =                               size,
                 tooltip   =  compare + zigzag.get(offset + 0).str + '\n' + str.tostring(zigzag.get(offset + 0).y)) 
    draw

// @function         create point based on zigzag array
// @param   zigzag   original array
// @param   offset   default value zero
// @returns          new point
method createPoint(point[] zigzag = na, int offset = 0) =>
    point.new(
     x   =       int(zigzag.get(offset + 0).x),
     y   =           zigzag.get(offset + 0).y,
     sty =           zigzag.get(offset + 0).sty,
     col =           zigzag.get(offset + 0).col,
     str =           zigzag.get(offset + 0).str)

// @function         create line based on zigzag array
// @param   zigzag   original array
// @param   width    line thickness
// @param   style    line style
// @param   offset   default value zero
// @returns          new line
method createLine(point[] zigzag = na, int width = na, string style = na, int offset = 0, bool enable = true, color col = chart.fg_color) =>
    line.new(x1    = int(zigzag.get(offset + 1).x),
             y1    =     zigzag.get(offset + 1).y,
             x2    = int(zigzag.get(offset + 0).x),
             y2    =     zigzag.get(offset + 0).y,
             xloc  =        xloc.bar_index,
             color =  enable ? zigzag.get(offset + 0).col : col,
             style = style,
             width = width)
//#endregion

//#region ———————————————————— 5. Execution
if ph or pl
    if dirchanged
        zigzag.initialize( varSetup, 4)
    else
        zigzag.update(varSetup, 4, dir)
//#endregion

//#region ———————————————————— 6. Constructs
var dirLine  = array.new<line>()
var dirLabel = array.new<label>()
var dirPoint = array.new<point>()

if zigzag.size() >= 3 
    [boolOr, boolAnd]    = zigzag.boolPoint(oldzigzag)
    if boolOr
        if boolAnd
            line.delete(dirLine.shift())
            label.delete(dirLabel.shift())
            dirPoint.unshift(zigzag.createPoint(0))
        if showLabel
            dirLabel.unshift(zigzag.createLabel(fontSize, 0, true))
        else
            dirLabel.unshift(na)
        if showLine 
            dirLine.unshift(zigzag.createLine(width, switchLine, 0, true))
        else
            dirLine.unshift(na)

var tbl = table.new(position.top_right, 3, bar + 3, border_width = 1)
if barstate.islast
    for i = dirLine.size() - 1 to bar + 1
        line.delete(array.get(dirLine, i))
    for i = dirLabel.size() - 1 to bar + 2
        label.delete(array.get(dirLabel, i))
    if showTable != 'None'    
        tbl.cell(0, 0,  'SELECTED', text_color = color.white, text_size = fontSize , bgcolor = color.black)
        tbl.cell(0, 1, 'BAR_INDEX', text_color = color.white, text_size = fontSize , bgcolor = color.black)
        tbl.cell(1, 1,     'PRICE', text_color = color.white, text_size = fontSize , bgcolor = color.black)
    if showTable == 'Label'
        tbl.cell(2, 1, 'HIGH LOW', text_color = color.white, text_size = fontSize, bgcolor = color.black)
        tbl.cell(1, 0,    'LABEL', text_color = color.white, text_size = fontSize, bgcolor = color.blue)
        tbl.merge_cells(1, 0, 2, 0)
        for i = 0 to bar
            tbl.cell(0, i + 2, str.tostring(label.get_x(   dirLabel.get(i))), text_color = color.black, text_size = fontSize, bgcolor = str.endswith(label.get_text(dirLabel.get(i)), 'H') ? colorUp : colorDn)
            tbl.cell(1, i + 2, str.tostring(label.get_y(   dirLabel.get(i))), text_color = color.black, text_size = fontSize, bgcolor = str.endswith(label.get_text(dirLabel.get(i)), 'H') ? colorUp : colorDn)
            tbl.cell(2, i + 2, str.tostring(label.get_text(dirLabel.get(i))), text_color = color.black, text_size = fontSize, bgcolor = str.endswith(label.get_text(dirLabel.get(i)), 'H') ? colorUp : colorDn)
    if showTable == 'Line'
        tbl.cell(1, 0,     'LINE', text_color = color.white, text_size = fontSize, bgcolor = color.blue)
        for i = 0 to bar
            tbl.cell(0, i + 2, str.tostring(line.get_x2(   dirLine.get(i))), text_color = color.black, text_size = fontSize, bgcolor = color.white)
            tbl.cell(1, i + 2, str.tostring(line.get_y2(   dirLine.get(i))), text_color = color.black, text_size = fontSize, bgcolor = color.white)
//#endregion