Object: object oriented programming made possible!
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © NeoDaNomad

//@version=5
library("Object",overlay=true)

//flags
    //collision flag comes before type flags
string f_collision = "C!"
string f_string = "S."
string f_float = "F."
string f_bool = "B."
string f_int = "I."

//separators
string keyVal = ":"
string comma = ","
string undr = "_"
string clsn = "|"

//format properties so that data can be output in the same type it as input (not a string you would have to convert)
export prop_string(string key, string val)=>
    //string flag added
    key+keyVal+f_string+val
    
export prop_float(string key, float val)=>
    //float flag added
    key+keyVal+f_float+str.tostring(val)
    
export prop_bool(string key, bool val)=>
    //bool flag added
    key+keyVal+f_bool+str.tostring(val)
    
export prop_int(string key, int val)=>
    //int flag added
    key+keyVal+f_int+str.tostring(val)


//only intended for use by Object methods but it's here
export ___HASH(string[] tableID, string str, bool isKeyVal)=>
    //all hash table state kept at index 0, cannont be overwritten
    string[] state = str.split(array.get(tableID,0),undr)
    //predetermined table sizes for consistent scaling
    string primeSequence = array.get(state,0)
    //the current index corresponding to the table size
    int currPrimeIndex = int(str.tonumber(array.get(state,1)))
    //the next table size in the prime sequence
    float nextPrime = str.tonumber(array.get(str.split(primeSequence, comma), currPrimeIndex + 1))
    //actual table size
    int tableSize=array.size(tableID)
    
    //used to calculate load factor
    string bucketsUsed = array.get(state,2)
    int entries = str.length(bucketsUsed) > 1 ? array.size(str.split(bucketsUsed, comma)) : 1
    int buckets = int(str.tonumber(array.get(str.split(primeSequence, comma),currPrimeIndex)))
    float loadFactor = entries/buckets
    // hash init
    int hash=7
    //checks for correct key:val format, if just a key is necessary, throws error if format isn't correct
    string key = ""
    string[] keyIterator = array.new_string()
    string val = ""
    if isKeyVal == true and str.contains(str,keyVal)
        key := array.get(str.split(str,keyVal),0)
        keyIterator := str.split(key,"")
        val := array.get(str.split(str,keyVal),1)
    else if isKeyVal == false
        key := str
        keyIterator := str.split(key,"")
    else
        runtime.error("string must contain key:value pair separated by ':'!")
        
    // resizes hash table if load factor reaches threshold
    if loadFactor > 0.67
        while tableSize < nextPrime
            array.push(tableID, na)
        //updates state for resized hash table
        array.set(tableID,0,primeSequence+undr+str.tostring(currPrimeIndex + 1)+undr+bucketsUsed)
    
    for char in keyIterator
        charset_str = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
        chars = str.split(charset_str, "")
        code_base = 32
        i = array.indexof(chars, char)
        code = i >= 0 ? code_base + i : na
        //hash initialized as a static prime number, then multiplied by the char code for each character in the "key" string
        hash := hash * code
    //applies constraints: hash < tableSize & hash > 0 (state is stored at index 0)
    hash := hash % tableSize == tableSize - 1 ? hash % tableSize : hash % tableSize + 1
    h = str.tostring(hash)
    [_h,_k,_val] = if isKeyVal == true 
        [h,key,val]
    else 
        [h,na,na]

export new()=>
    string primeSequence = "113,229,463,863,1733,3511,7103,9973"
    string currPrimeIndex = "0"
    string prime = array.get(str.split(primeSequence, comma), int(str.tonumber(currPrimeIndex)))
    init = array.new_string(int(str.tonumber(prime)),na)
    string bucketsUsed = "0"
    string state = primeSequence+undr+currPrimeIndex+undr+bucketsUsed
    array.set(init,0,state)
    map = init

export set(string[] ID, string str)=>
    string[] state = str.split(array.get(ID,0),undr)
    string primeSequence = array.get(state,0)
    string currPrimeIndex = array.get(state,1)
    string bucketsUsed = array.get(state,2)
    string newBuckets = ""
    
    [hashString,key,val] = ___HASH(ID, str, true)
    hash = int(str.tonumber(hashString))
    // value currently stored at hash index (most likely na)
    string val1 = array.get(ID, hash)
    //checks for collision (more than one entry at same hash index)
    if val1 != na
        // checks if collision is the first instance
        if str.match(val1, clsn) == na
            array.set(ID, hash, f_collision+val1+clsn+key+keyVal+val)
            //collision flag added if collision is the first instance
            newBuckets := str.format("{{0},{1}{2}",bucketsUsed,f_collision,hash)
        else
            //if not the collision flag will already be present and collided entry will be chained to all other entries at hash index of table using "clsn" separator
            array.set(ID, hash, val1+clsn+key+keyVal+val)
            newBuckets := str.format("{{0},{1}{2}",bucketsUsed,f_collision,hash)
    else
        array.set(ID,hash,key+keyVal+val)
        newBuckets := str.format("{0},{1}",bucketsUsed,hash)
    array.set(ID, 0, primeSequence+undr+currPrimeIndex+undr+newBuckets)
    

export get_f(string[] ID, string key)=>
    
    [hashString,_,__] = ___HASH(ID, key, false)
    hash = int(str.tonumber(hashString))
    //check for key in hash table
    val = array.get(ID,hash)
    props = array.new_string(0)
    
    //init extraction variables
    f_collision_split = array.new_string(0)
    _prop = array.new_string(0)
    vt = array.new_string(0)
    shell = ""
    v = ""
    type = ""
    _val = ""
    _key = ""
    if val == na
        runtime.error(str.format("key: '{0}' is invalid! Check spelling and try again.", key))
    //check for collision at hash index
    else if str.startswith(val, f_collision)
        f_collision_split := str.split(val, f_collision)
        shell := array.get(f_collision_split,1)
        props := str.split(shell,clsn)
    else
        array.push(props, val)
    
    for property in props
        _prop := str.split(property, keyVal)
        _key := array.get(_prop, 0)
        if key != _key
            continue
        v := array.get(_prop, 1)
        vt := str.split(v, ".")
        type := array.get(vt, 0)
        _val := array.get(vt, 1)
        break
    //return original-typed value
    switch
        type == "B" => runtime.error(str.format("Wrong get method! expecting get_{0}, called get_{1}",str.lower(type), "f"))
        type == "S" => runtime.error(str.format("Wrong get method! expecting get_{0}, called get_{1}",str.lower(type), "f"))
        type == "I" => runtime.error(str.format("Wrong get method! expecting get_{0}, called get_{1}",str.lower(type), "f"))
    res = str.tonumber(_val)
        
export get_s(string[] ID, string key)=>
    
    [hashString,_,__] = ___HASH(ID, key, false)
    hash = int(str.tonumber(hashString))
    //check for key in hash table
    val = array.get(ID,hash)
    props = array.new_string(0)
    
    //init extraction variables
    f_collision_split = array.new_string(0)
    _prop = array.new_string(0)
    vt = array.new_string(0)
    shell = ""
    v = ""
    type = ""
    _val = ""
    _key = ""
    if val == na
        runtime.error(str.format("key: '{0}' is invalid! Check spelling and try again.", key))
    //check for collision at hash index
    else if str.startswith(val, f_collision)
        f_collision_split := str.split(val, f_collision)
        shell := array.get(f_collision_split,1)
        props := str.split(shell,clsn)
    else
        array.push(props, val)
    
    for property in props
        _prop := str.split(property, keyVal)
        _key := array.get(_prop, 0)
        if key != _key
            continue
        v := array.get(_prop, 1)
        vt := str.split(v, ".")
        type := array.get(vt, 0)
        _val := array.get(vt, 1)
        break
    //return original-typed value
    switch
        type == "B" => runtime.error(str.format("Wrong get method! expecting get_{0}, called get_{1}",str.lower(type), "s"))
        type == "F" => runtime.error(str.format("Wrong get method! expecting get_{0}, called get_{1}",str.lower(type), "s"))
        type == "I" => runtime.error(str.format("Wrong get method! expecting get_{0}, called get_{1}",str.lower(type), "s"))
    res = _val
    
export get_b(string[] ID, string key)=>
    
    [hashString,_,__] = ___HASH(ID, key, false)
    hash = int(str.tonumber(hashString))
    //check for key in hash table
    val = array.get(ID,hash)
    props = array.new_string(0)
    
    //init extraction variables
    f_collision_split = array.new_string(0)
    _prop = array.new_string(0)
    vt = array.new_string(0)
    shell = ""
    v = ""
    type = ""
    _val = ""
    _key = ""
    if val == na
        runtime.error(str.format("key: '{0}' is invalid! Check spelling and try again.", key))
    //check for collision at hash index
    else if str.startswith(val, f_collision)
        f_collision_split := str.split(val, f_collision)
        shell := array.get(f_collision_split,1)
        props := str.split(shell,clsn)
    else
        array.push(props, val)
    
    for property in props
        _prop := str.split(property, keyVal)
        _key := array.get(_prop, 0)
        if key != _key
            continue
        v := array.get(_prop, 1)
        vt := str.split(v, ".")
        type := array.get(vt, 0)
        _val := array.get(vt, 1)
        break
    //return original-typed value
    switch
        type == "S" => runtime.error(str.format("Wrong get method! expecting get_{0}, called get_{1}",str.lower(type), "b"))
        type == "F" => runtime.error(str.format("Wrong get method! expecting get_{0}, called get_{1}",str.lower(type), "b"))
        type == "I" => runtime.error(str.format("Wrong get method! expecting get_{0}, called get_{1}",str.lower(type), "b"))
    res = _val == "true" ? true : false
    
export get_i(string[] ID, string key)=>
    
    [hashString,_,__] = ___HASH(ID, key, false)
    hash = int(str.tonumber(hashString))
    //check for key in hash table
    val = array.get(ID,hash)
    props = array.new_string(0)
    
    //init extraction variables
    f_collision_split = array.new_string(0)
    _prop = array.new_string(0)
    vt = array.new_string(0)
    shell = ""
    v = ""
    type = ""
    _val = ""
    _key = ""
    if val == na
        runtime.error(str.format("key: '{0}' is invalid! Check spelling and try again.", key))
    //check for collision at hash index
    else if str.startswith(val, f_collision)
        f_collision_split := str.split(val, f_collision)
        shell := array.get(f_collision_split,1)
        props := str.split(shell,clsn)
    else
        array.push(props, val)
    
    for property in props
        _prop := str.split(property, keyVal)
        _key := array.get(_prop, 0)
        if key != _key
            continue
        v := array.get(_prop, 1)
        vt := str.split(v, ".")
        type := array.get(vt, 0)
        _val := array.get(vt, 1)
        break
    //return original-typed value
    switch
        type == "S" => runtime.error(str.format("Wrong get method! expecting get_{0}, called get_{1}",str.lower(type), "i"))
        type == "F" => runtime.error(str.format("Wrong get method! expecting get_{0}, called get_{1}",str.lower(type), "i"))
        type == "B" => runtime.error(str.format("Wrong get method! expecting get_{0}, called get_{1}",str.lower(type), "i"))
    
    res = int(str.tonumber(_val))
    
// [COMING SOON...] remove()=>
// [COMING SOON...] size()=>
// [COMING SOON...] keys()=>
// [COMING SOON...] values()=>
// [COMING SOON...] fromArray()=>
// [COMING SOON...] fromString()=>
// [COMING SOON...] toString()=>
// [COMING SOON...] toJSON()=>


//EXAMPLE
obj = new()
set(obj, prop_string("hello", "world"))
greeting = str.format("hello {0}!",get_s(obj, "hello"))

greet=label.new(barstate.islast?bar_index:na,high,yloc=yloc.abovebar,text=greeting,size=size.huge)
label.set_color(greet,color.yellow)

plot(high,display=display.none)



