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

//@version=5

//============================================
// @description Json dictionary functions, create, format, and write.
library("json")



//  new (name , kind ) -> object  
//     defines a new obj. each obbj can be :                 
//    - _OBJECT to contain sub obj's                         
//    - _ARRAY  for json array (can contain obbjs or items)  
//    - unspecified: automatic int/float/bool/srting         

//    set    (_item     , _obj      , _key   ) -> set item to parent obj
//    add    (_obj      , _key      , _item  ) -> from object, add an item
//    set    (_child    , _Guardians            ) -> set child object to a parent
//    add    (_Guardians   , _child             ) -> to _parentt object, add a child
//    write  (object                         ) -> convert any obbject at any level tto json


//============================================
//============================================
//      Imports, User Defined Type
//         Helpers, Formatters
//#region  
//============================================
import kaigouthro/into/2
import kaigouthro/font/5
import kaigouthro/catchChecks/1 as chk

// @type   obj    Object storage container/item
// @field  key    (string   ) item name 
// @field  kind   (string   ) item's type(for writing)
// @field  item   (string   ) item (converted to string)
// @field  keys   (string[] ) keys of all sub-items and objects 
// @field  items  (obj[]    ) nested obj off individual subitems (for later...) 
export type  obj 
    string   key  = ''
    string   kind = ''
    string   item = ''
    string[] keys
    obj   [] items
    bool     flag


// @function create multitype object
// @pparam _name (string) Name off object
// @pparam _kind (string) Preset Type (_OBJECT if a container item)
// @returns object container/item 2-in-1
export new(string _name, string _kind = '') =>
    var key     = _name
    var kind    = _kind
    var item    = ''
    var keys    = array.new <string> ( 0 )
    var items   = array.new <obj>    ( 0 )
    obj     = obj.new  ( key,kind,item,keys,items)
    obj

// @variable Type/Kind key for storage/writing
var _OBJECT  = 'object'
// @variable Type/Kind key for storage/writing
var _STRING  = 'string'
// @variable Type/Kind key for storage/writing
var _BOOL    = 'bool'
// @variable Type/Kind key for storage/writing
var _INT     = 'integer'
// @variable Type/Kind key for storage/writing
var _FLOAT   = 'float'
// @variable Type/Kind key for storage/writing
var _ARRAY   = 'array'
// @variable Type/Kind key for storage/writing
var _TOPLEVEL= 'parentOBJ'

//@helper shortcuts
f(x)  =>  into.f ( x )
i(x)  =>  into.i ( x )
b(x)  =>  into.b ( x )
s(x)  =>  into.s ( x )

//@helpers for index / adding / setting / compacted
_pushnew  ( obj _o ,_k,_sz ) =>      array.push(_o.keys , _k ), array.push(_o.items , new(_k)),_sz 
_getkey   ( obj _o ,_k     ) => _sz= array.size(_o.keys),_idx = array.includes(_o.keys,_k) ? (array.indexof ( _o.keys , _k  )) : na, na(_idx) ? _pushnew(_o,_k,_sz): _idx
_doadd    ( obj _o , string  _k , obj _i ,string _t) => _id  = _getkey (_o,_k) ,_i.kind:=_t,array.set ( _o.items , _id , _i )
_doadd    ( obj _o , string  _k , _i     ) => _id  = _getkey (_o,_k),_obj = array.get ( _o.items , _id ) ,_obj.item := s(_i),_obj.kind := chk.typeIs(_i,false),array.set ( _o.items , _id , _obj ) ,_obj
_rem      (obj _o,_idx) => array.remove  (_o.keys, _idx), array.remove(_o.items,_idx)
_array    ( obj _o ,_k,string[] _keys, obj[] _items ) =>   
    _id  = _getkey (_o,_k) ,_obj = array.get ( _o.items , _id )
    _arraysize = array.size(_obj.items) 
    _keysize   = math.min(array.size ( _keys),_arraysize)
    if _arraysize >0
        for _n = 0 to array.size (_obj.keys )
            _oldkey  = array.shift(_obj.keys)
            _olditem = array.shift(_obj.items)
            if array.includes(_keys,_oldkey)
                array.push(_obj.keys, _oldkey)
                array.push(_obj.items, _olditem)
    for [_n, item] in _items
        key = array.get(_keys,_n)
        _doadd(_obj,key,item,item.kind)           
    array.set ( _o.items , _id ,  _obj)
_array    ( obj _o ,_k,string[] _keys, _items, string _type ) =>   
    _id  = _getkey (_o,_k) ,_obj = array.get ( _o.items , _id )
    _arraysize = array.size(_obj.items) 
    _keysize   = math.min(array.size ( _keys),_arraysize)
    _obj.kind  :=_type != _ARRAY ? _ARRAY : _OBJECT  
    if _arraysize >0
        for _n = 0 to array.size (_obj.keys )
            _oldkey  = array.shift(_obj.keys)
            _olditem = array.shift(_obj.items)
            if array.includes(_keys,_oldkey)
                array.push(_obj.keys, _oldkey)
                array.push(_obj.items, _olditem)
    for [_n, item] in _items
        key = array.get(_keys,_n)
        _itm = _doadd(_obj,key,item)
        _itm.key := key
    array.set ( _o.items , _id , _obj)

//============================================
//#endregion
//#region  
//============================================

// @function Formats JSON for presentable output
// @param _input (string) json string
// @returns cleaned string
export format  (string _input) =>
    data = _input
    for _char in     array.from('\n','\t')
        data := str.replace_all(data ,_char,'')
    _str      = '', inc = 0, gu = false
    _length   = str.length(data)
    for i     = 0 to _length -1
        s     = str.substring(data,i,i+1)
        _str +=  switch
            not gu and str.contains(','  ,s) => s    +        font.indent(inc)
            not gu and str.contains('{[' ,s) => inc += 1 ,s + font.indent(inc)
            not gu and str.contains('}]' ,s) => inc -= 1,     font.indent(inc) + s
            str.contains           ('\'"',s) => gu  := gu ?   false :  true   ,  s
            => s
    _str

// // @function Write object to string Object
// // @param  _object     (obj)
// // @param  _key        (array<(string/int)> )/(string)   
// // @param  _itemname   (string)
export write ( obj  _item ) =>
    item          = obj.copy(_item)
    item.flag    := true
    stack         = array.new<obj>(1, item )
    out           = "{"
    while array.size ( stack ) > 0
        current   =  array.get(stack, 0) 
        _type     =  current.kind
        _l        =  _type == _OBJECT ?  '{' : _type == _ARRAY  ?  '[' : ''  
        _r        =  _type == _OBJECT ?  '}' : _type == _ARRAY  ?  ']' : ''  
        size      =  array.size(current.items) 
        strlen    =  str.length(out)
        if size   == 0  and current.flag    
            comma     = str.contains('{[',str.substring(out,strlen-1)) ? '' : ','
            out   += comma + '"'+current.key+'":'+  (_type == _STRING ? '"'+current.item+'"': current.item)
            current.flag   := false
            array.shift(stack)
        else
            if current.flag
                if size > 0
                    comma          = str.contains('{[',str.substring(out,strlen-1)) ? '' : ','
                    out           += comma + '"' + current.key +'":'+ (current.kind == _OBJECT ?  '{' : _type == _ARRAY  ?  '[' : ''  )
                    for i          = size- 1 to 0
                        itm        = array.get(current.items,i)
                        itm.flag  :=  true                    
                        array.unshift(stack, itm)         
                    current.flag      :=  false
            else
                out += ''  + _r
                array.shift(stack)
    out += '}'


// @function Remove an object (Wipes it and all children out)
// @param  obj  ( obj multi-type-item object)
// @param  key  ( string/int)         
export remove (obj obj, string key ) => array.includes(obj.keys,key)?_rem(obj,array.indexof(obj.keys,key)):na
export remove (obj obj, int    key ) => array.includes(obj.keys,s(key))?_rem(obj,array.indexof(obj.keys,s(key))):na

// @function Add any or array of any => int/float/string/bool/object
// @param   _obj     ( obj multi-type-item object)
// @param   _key     ( string/int)         
// @param   _item    ( int / float / bool / string  )         
// @param   _items   array of ( int / float / bool / string  )         
export add ( obj     _obj , int      _key   , int     _item ) =>  _doadd ( _obj ,s(_key) , _item )
export add ( obj     _obj , int      _key   , float   _item ) =>  _doadd ( _obj ,s(_key) , _item )
export add ( obj     _obj , int      _key   , bool    _item ) =>  _doadd ( _obj ,s(_key) , _item )
export add ( obj     _obj , int      _key   , string  _item ) =>  _doadd ( _obj ,s(_key) , _item )
export add ( obj     _obj , string   _key   , int     _item ) =>  _doadd ( _obj ,   _key , _item )
export add ( obj     _obj , string   _key   , float   _item ) =>  _doadd ( _obj ,   _key , _item )
export add ( obj     _obj , string   _key   , bool    _item ) =>  _doadd ( _obj ,   _key , _item )
export add ( obj     _obj , string   _key   , string  _item ) =>  _doadd ( _obj ,   _key , _item )
export add ( obj     _obj , string   _key   , string[] _keys, obj    [] _items  ) =>  _array ( _obj , _key,  _keys, _items)
export add ( obj     _obj , string   _key   , string[] _keys, string [] _items  ) =>  _array ( _obj , _key,  _keys, _items,chk.typeIs(_items,false))
export add ( obj     _obj , string   _key   , string[] _keys, int    [] _items  ) =>  _array ( _obj , _key,  _keys, _items,chk.typeIs(_items,false))
export add ( obj     _obj , string   _key   , string[] _keys, bool   [] _items  ) =>  _array ( _obj , _key,  _keys, _items,chk.typeIs(_items,false))
export add ( obj     _obj , string   _key   , string[] _keys, float  [] _items  ) =>  _array ( _obj , _key,  _keys, _items,chk.typeIs(_items,false))
export add ( obj     _obj , int      _key   , string[] _keys, obj    [] _items  ) =>  _array ( _obj ,  s(_key),  _keys, _items)
export add ( obj     _obj , int      _key   , string[] _keys, string [] _items  ) =>  _array ( _obj ,  s(_key),  _keys, _items,chk.typeIs(_items,false))
export add ( obj     _obj , int      _key   , string[] _keys, int    [] _items  ) =>  _array ( _obj ,  s(_key),  _keys, _items,chk.typeIs(_items,false))
export add ( obj     _obj , int      _key   , string[] _keys, bool   [] _items  ) =>  _array ( _obj ,  s(_key),  _keys, _items,chk.typeIs(_items,false))
export add ( obj     _obj , int      _key   , string[] _keys, float  [] _items  ) =>  _array ( _obj ,  s(_key),  _keys, _items,chk.typeIs(_items,false))

// @function  Add a  object as a subobject to storage 
// @param _Guardians to insert obj into
// @param  _child  to be inserted
export add   ( obj  _Guardians , obj  _child   ) => _doadd ( _Guardians  ,  _child.key ,  _child,_child.kind)


// @function Set item to an object (here in preparation for future Pine methods)
// @param   _obj     ( obj multi-type-item object)
// @param   _key     ( string/int)         
// @param   _item    ( int / float / bool / string  )         
// @param   _items   array of ( int / float / bool / string  )         
export set ( int       _item , obj _obj  , int     _key  ) =>  _doadd ( _obj ,s(_key) , _item )
export set ( float     _item , obj _obj  , int     _key  ) =>  _doadd ( _obj ,s(_key) , _item )
export set ( bool      _item , obj _obj  , int     _key  ) =>  _doadd ( _obj ,s(_key) , _item )
export set ( string    _item , obj _obj  , int     _key  ) =>  _doadd ( _obj ,s(_key) , _item )
export set ( int       _item , obj _obj  , string  _key  ) =>  _doadd ( _obj ,s(_key) , _item )
export set ( float     _item , obj _obj  , string  _key  ) =>  _doadd ( _obj ,s(_key) , _item )
export set ( bool      _item , obj _obj  , string  _key  ) =>  _doadd ( _obj ,s(_key) , _item )
export set ( string    _item , obj _obj  , string  _key  ) =>  _doadd ( _obj ,s(_key) , _item )
export set ( obj    [] _items, string[]  _keys,  obj _obj , string   _key     ) =>  _array ( _obj , _key,  _keys, _items)
export set ( string [] _items, string[]  _keys,  obj _obj , string   _key     ) =>  _array ( _obj , _key,  _keys, _items,chk.typeIs(_items,false))
export set ( int    [] _items, string[]  _keys,  obj _obj , string   _key     ) =>  _array ( _obj , _key,  _keys, _items,chk.typeIs(_items,false))
export set ( bool   [] _items, string[]  _keys,  obj _obj , string   _key     ) =>  _array ( _obj , _key,  _keys, _items,chk.typeIs(_items,false))
export set ( float  [] _items, string[]  _keys,  obj _obj , string   _key     ) =>  _array ( _obj , _key,  _keys, _items,chk.typeIs(_items,false))
export set ( obj    [] _items, string[]  _keys,  obj _obj , int      _key     ) =>  _array ( _obj ,  s(_key),  _keys, _items)
export set ( string [] _items, string[]  _keys,  obj _obj , int      _key     ) =>  _array ( _obj ,  s(_key),  _keys, _items,chk.typeIs(_items,false))
export set ( int    [] _items, string[]  _keys,  obj _obj , int      _key     ) =>  _array ( _obj ,  s(_key),  _keys, _items,chk.typeIs(_items,false))
export set ( bool   [] _items, string[]  _keys,  obj _obj , int      _key     ) =>  _array ( _obj ,  s(_key),  _keys, _items,chk.typeIs(_items,false))
export set ( float  [] _items, string[]  _keys,  obj _obj , int      _key     ) =>  _array ( _obj ,  s(_key),  _keys, _items,chk.typeIs(_items,false))

// @function  Add a solitary object as a subobject to storage 
// @param    _child  to be inserted
// @param    _Guardians to insert obj into
export set   ( obj  _child  , obj  _Guardians  ) => _doadd ( _Guardians  ,  _child.key ,  _child, _child.kind)

//============================================
//#endregion  
//============================================




// testing / demo
var _GrandParent   = new (  'GrandParent' , _OBJECT  )
var _Guardians     = new (  'Childcare'   , _OBJECT  ) 
var _child         = new (  'Sibling'     , _OBJECT  )
var _stepparent    = new (  'StepParent'  , _OBJECT  ) 
var _child2        = new (  'StepSibling' , _OBJECT  )

var _floatkeys = array.from('capricious','abaft','pizzas','borrow','cause','fuel','hallowed','abrupt','finger','rely','head','valuable')
var _floatarr = array.from(3.96,9.63,9.06,7.47,4.38,7.91,3.29,2.86,9.11,7.20,4.01,4.34)

// test label
var label[] lbls = array.new<label>(4)

refresh(_s,i)=>array.set(lbls,i,label.new(last_bar_index - 10*i  , 0, font.uni(_s,16),
      xloc.bar_index, yloc.price,#a4e5e8e7, label.style_label_upper_left, 
      textcolor = #0d0227,  textalign = text.align_left, size=size.normal))

if barstate.islast

    // Generic Example for Alerts, Create your owm!

    // set items, using strings as keys, these are just examples.
    // (for a future method, add/set are same/swapped order)    
    
    add ( _GrandParent  ,  'Mom'         ,  'Susan'      )
    add ( _GrandParent  ,  'Dad'         ,  'John'       )      
    add ( _Guardians    ,  'Babysitter'  ,  'Bianca'     )      
        
    add ( _child        ,  'Daughter'  ,  'Suzie'        )      
    add ( _child        ,  'Son'       ,  'Johnny'       )      
    

    // family assembled
    add ( _GrandParent  ,  _Guardians     )
    add ( _Guardians    ,  _child      )
          
    refresh( ' _Guardian \n\n'             + format(write (_Guardians      )), 2)
        
   
    // some assorted testing.. and overwwrite on add
    add ( _GrandParent , 'parent level item1'   ,  'an item'  )
    add ( _GrandParent , 'parent level item2'   ,  'an item'  )
        
    add ( _GrandParent , 'Overwrwite test' ,  "true"        )
    add ( _GrandParent , 'Overwrwite test' ,  2             )
    add ( _GrandParent , 'Overwrwite test' ,  3             )
    add ( _GrandParent , 'Overwrwite test' ,  4             )
    add ( _GrandParent , 'Overwrwite test' ,  1234          )

    refresh( '_GrandParent   \n\n '  + format(write (_GrandParent )), 3)
          
    // Susan Didn't Invest wisely..
    remove ( _Guardians     ,  'Babysitter')
    add    ( _GrandParent   ,  _stepparent )
    add    ( _stepparent    ,  _child2     )
    add    ( _child2        ,  'StepBrother',  'Jonathon'   )      
    
    
    refresh( '_GrandParent \n\n'   + format(write (_GrandParent )), 1)
    add    ( _GrandParent    ,  'Mom'       ,  'Bianca'     )      

    // check that it adds after the fast and still works
    add ( _child2   , 'All the Toys??',  _floatkeys , _floatarr )

    refresh( '_GrandParent \n\n'  + format(write (_GrandParent     )), 0)