# This scipt is for creating and using as many counters as you need.
#
# Written by HighMoon - Malcolm Greene
# Current Version : v1.2
# Changes in v1.1:
#   Updated save and load functions to preserve the deicmals, was previouse using int instead of double
# Changes in v1.2:
#   Updated the output function due to formatting issues, also added a property so you can specify the number of decimal places
#   Also added locale function to keep number local to user.

import obspython as obs
import math
import locale
import sys

sysDecimal = "."

totalCounters = 1
counters = []

class counterClass:
    hotKeyInc = obs.OBS_INVALID_HOTKEY_ID
    hotKeyDec = obs.OBS_INVALID_HOTKEY_ID
    hotKeyRst = obs.OBS_INVALID_HOTKEY_ID
    stepAmount = 1
    count = 0
    displaySource = ""
    name = "counterName"
    padChar = "0"
    minVal = 0
    maxVal = 99
    
    placesAfter = 2
    
    def __init__(self, cntName):
        self.name = cntName
        
    def saveCounter(self, settings):
        hotKeySaveArray = obs.obs_hotkey_save(self.hotKeyInc)
        obs.obs_data_set_array(settings, self.name+"_inc", hotKeySaveArray)
        obs.obs_data_array_release(hotKeySaveArray)
        
        hotKeySaveArray = obs.obs_hotkey_save(self.hotKeyDec)
        obs.obs_data_set_array(settings, self.name+"_dec", hotKeySaveArray)
        obs.obs_data_array_release(hotKeySaveArray)
        
        hotKeySaveArray = obs.obs_hotkey_save(self.hotKeyRst)
        obs.obs_data_set_array(settings, self.name+"_rst", hotKeySaveArray)
        obs.obs_data_array_release(hotKeySaveArray)
        
        obs.obs_data_set_double(settings,self.name+"_count",self.count)
        obs.obs_data_set_string(settings,self.name+"_source",self.displaySource)
        obs.obs_data_set_string(settings,self.name+"_pad_char", self.padChar)
        obs.obs_data_set_double(settings, self.name+"_min_val", self.minVal)
        obs.obs_data_set_double(settings, self.name+"_max_val", self.maxVal)
        obs.obs_data_set_double(settings, self.name+"_step_val", self.stepAmount)
        obs.obs_data_set_int(settings, self.name+"_places_after", self.placesAfter)
    
    def loadCounter(self, settings, counterName):
        self.name = counterName
        self.count = obs.obs_data_get_double(settings, counterName+"_count")
        self.displaySource = obs.obs_data_get_string(settings, counterName+"_source")
        self.padChar = obs.obs_data_get_string(settings, counterName+"_pad_char")
        self.minVal = obs.obs_data_get_double(settings, counterName+"_min_val")
        self.maxVal = obs.obs_data_get_double(settings, counterName+"_max_val")
        self.stepAmount = obs.obs_data_get_double(settings, counterName+"_step_val")
        self.placesAfter = obs.obs_data_get_int(settings, counterName+"_places_after")
        
        def inc_counter(pressed):
            if pressed:
                self.count = self.count + self.stepAmount
                if self.count > self.maxVal:
                    self.count = self.maxVal
                self.updateText()
            
        self.hotKeyInc = obs.obs_hotkey_register_frontend(counterName+"_inc", "Increment Counter "+counterName, inc_counter)
        hotKeySaveArray = obs.obs_data_get_array(settings, counterName+"_inc")
        obs.obs_hotkey_load(self.hotKeyInc, hotKeySaveArray)
        obs.obs_data_array_release(hotKeySaveArray)
        
        def dec_counter(pressed):
            if pressed:
                self.count = self.count - self.stepAmount
                if self.count < self.minVal:
                    self.count = self.minVal
                self.updateText()
            
        self.hotKeyDec = obs.obs_hotkey_register_frontend(counterName+"_dec", "Decrement Counter "+counterName, dec_counter)
        hotKeySaveArray = obs.obs_data_get_array(settings, counterName+"_dec")
        obs.obs_hotkey_load(self.hotKeyDec, hotKeySaveArray)
        obs.obs_data_array_release(hotKeySaveArray)
        
        def rst_counter(pressed):
            if pressed:
                self.count = self.minVal
                self.updateText()
            
        self.hotKeyRst = obs.obs_hotkey_register_frontend(counterName+"_rst", "Reset Counter "+counterName, rst_counter)
        hotKeySaveArray = obs.obs_data_get_array(settings, counterName+"_rst")
        obs.obs_hotkey_load(self.hotKeyRst, hotKeySaveArray)
        obs.obs_data_array_release(hotKeySaveArray)
        
        return settings
    
    def updateText(self):
        settings = obs.obs_data_create()
        pad=""
        maxLen = 0
        minLen = 0
        numLen = 0
        output = ""
        
        source = obs.obs_get_source_by_name(self.displaySource)
        
        if hasDecimalVal(self.count) or hasDecimalVal(self.maxVal) or hasDecimalVal(self.minVal) or hasDecimalVal(self.stepAmount) or self.placesAfter > 0:
            output = locale.format_string("%."+str(self.placesAfter)+"f", self.count)
        else:
            output = locale.format_string("%.0f", self.count)
        
        numLen = len(str(getIntPart(self.count, self.placesAfter)))
        maxLen = len(str(getIntPart(self.maxVal, self.placesAfter)))
            
        pad = self.padChar * (maxLen - numLen)
            
        obs.obs_data_set_string(settings, "text", pad+output)
        obs.obs_source_update(source, settings)
        obs.obs_data_release(settings)
        obs.obs_source_release(source)    
    
def script_load(settings):
    global totalCounters
    totalCounters = obs.obs_data_get_int(settings,"total_counters")
    for prepCounter in range(totalCounters):
        counter = counterClass("counter_"+str(prepCounter+1))
        counters.append(counter)
        counters[prepCounter].loadCounter(settings, "counter_"+str(prepCounter+1))
        counters[prepCounter].updateText()
    
    locale.setlocale(locale.LC_ALL,'')
    sysDecimal = locale.localeconv()["decimal_point"]
    
    obs.script_log(obs.LOG_INFO, "Counters script loaded")
    
def script_save(settings):
    for counter in counters:
        counter.saveCounter(settings)

def script_update(settings):
    global totalCounters
    oldTotal = totalCounters
    totalCounters = obs.obs_data_get_int(settings,"total_counters")
    
    for cntNum in range(totalCounters):
        if cntNum >= oldTotal:
            counter = counterClass("counter_"+str(cntNum+1))
            counter.displaySource = obs.obs_data_get_string(settings, "counter_"+str(cntNum+1)+"_source")
            counter.padChar = obs.obs_data_get_string(settings, "counter_"+str(cntNum+1)+"_pad_char")
            counter.minVal = obs.obs_data_get_double(settings, "counter_"+str(cntNum+1)+"_min_val")
            counter.maxVal = obs.obs_data_get_double(settings, "counter_"+str(cntNum+1)+"_max_val")
            counter.stepAmount = obs.obs_data_get_double(settings, "counter_"+str(cntNum+1)+"_step_val")
            counter.placesAfter = obs.obs_data_get_int(settings, "counter_"+str(cntNum+1)+"_places_after")
            counters.append(counter)
            counter.updateText()
        else:
            counters[cntNum].displaySource = obs.obs_data_get_string(settings, "counter_"+str(cntNum+1)+"_source")
            counters[cntNum].padChar = obs.obs_data_get_string(settings, "counter_"+str(cntNum+1)+"_pad_char")
            counters[cntNum].minVal = obs.obs_data_get_double(settings, "counter_"+str(cntNum+1)+"_min_val")
            counters[cntNum].maxVal = obs.obs_data_get_double(settings, "counter_"+str(cntNum+1)+"_max_val")
            counters[cntNum].stepAmount = obs.obs_data_get_double(settings, "counter_"+str(cntNum+1)+"_step_val")
            counters[cntNum].placesAfter = obs.obs_data_get_int(settings, "counter_"+str(cntNum+1)+"_places_after")
            counters[cntNum].updateText()
    

def script_properties():
    global totalCounters
    
    props = obs.obs_properties_create()
    obs.obs_properties_add_int(props, "total_counters", "Total Number of Counters", 1, 2147483647, 1)
    
    for makeCounter in range(totalCounters):
    
        p = obs.obs_properties_add_list(props, "counter_"+str(makeCounter+1)+"_source", "Text Source to display counter_"+str(makeCounter+1) , obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)
        
        sources = obs.obs_enum_sources()
        if sources is not None:
            for source in sources:
                source_id = obs.obs_source_get_id(source)
                if source_id == "text_gdiplus" or source_id == "text_ft2_source" or source_id == "text_gdiplus_v3":
                    name = obs.obs_source_get_name(source)
                    obs.obs_property_list_add_string(p, name, name)
        
        obs.source_list_release(sources)
    
        obs.obs_properties_add_text(props, "counter_"+str(makeCounter+1)+"_pad_char", "Pad Character",  obs.OBS_TEXT_DEFAULT)
        obs.obs_properties_add_int(props, "counter_"+str(makeCounter+1)+"_places_after", "Number of Decimal Places", 0, 2147483647, 1)
        obs.obs_properties_add_float(props, "counter_"+str(makeCounter+1)+"_min_val", "Counter Minimum Value", -2147483647, 2147483647, 1)
        obs.obs_properties_add_float(props, "counter_"+str(makeCounter+1)+"_max_val", "Counter Maximum Value", -2147483647, 2147483647, 1)
        obs.obs_properties_add_float(props, "counter_"+str(makeCounter+1)+"_step_val", "Counter Step Value", -2147483647, 2147483647, 1)
    
    return props
        
def script_description():
    return "Counter system that can handle many counters. \n Press the script refresh button after changing counter count. \n You can also hit refresh after changing other options force an update."
    
def hasDecimalVal(numToCheck):
    checkString = locale.format_string("%f", numToCheck)
    decPos = checkString.index(sysDecimal)
    decVal = int(checkString[decPos+1:])
    
    if decVal > 0:
        return True
        
    return False
    
def getIntPart(numToSplit, decPlaces):
    if decPlaces <= 0:
        return numToSplit
    numString = locale.format_string("%."+str(decPlaces)+"f", numToSplit)
    decPos = numString.index(sysDecimal)
    intVal = int(numString[:decPos])
    return intVal

def getDecPart(numToSplit, decPlaces):
    if decPlaces <= 0:
        return 0
    numString = locale.format_string("%."+str(decPlaces)+"f", numToSplit)
    decPos = numString.index(sysDecimal)
    decVal = int(numString[decPos+1:])    
    return decVal
    