# This scipt is for creating and using as many timers as you need.
# This will output to a text source.
#
# Written by HighMoon - Malcolm Greene
# v1.0

import obspython as obs
import locale
import time

totalTimers = 1
timers = []
sysDecimal = "."

class timerClass:
    hotKeyStart = obs.OBS_INVALID_HOTKEY_ID
    hotKeyStop = obs.OBS_INVALID_HOTKEY_ID
    hotKeyReset = obs.OBS_INVALID_HOTKEY_ID
    displaySource = ""
    name = "timerName"
    elapsed = 0
    startTime = 60
    endTime = 0
    lastUpdate = 0
    sysStartTime = 0
    running = False
    finished = False
    countDown = True
    endText = ""
    reset = False
    placesBefore  = 0
    placesAfter = 2
    
    def __init__(self, tmrName):
        self.name = tmrName
        
    def saveTimer(self, settings):
        hotKeySaveArray = obs.obs_hotkey_save(self.hotKeyStart)
        obs.obs_data_set_array(settings, self.name+"_start", hotKeySaveArray)
        obs.obs_data_array_release(hotKeySaveArray)
        
        hotKeySaveArray = obs.obs_hotkey_save(self.hotKeyStop)
        obs.obs_data_set_array(settings, self.name+"_stop", hotKeySaveArray)
        obs.obs_data_array_release(hotKeySaveArray)
        
        hotKeySaveArray = obs.obs_hotkey_save(self.hotKeyReset)
        obs.obs_data_set_array(settings, self.name+"_reset", hotKeySaveArray)
        obs.obs_data_array_release(hotKeySaveArray)
        
        obs.obs_data_set_int(settings,self.name+"_start_time",self.startTime)
        obs.obs_data_set_int(settings,self.name+"_end_time",self.endTime)
        obs.obs_data_set_string(settings,self.name+"_source",self.displaySource)
        obs.obs_data_set_string(settings,self.name+"_end_text",self.endText)
        

    
    def loadTimer(self, settings, timerName):
        self.name = timerName
        self.startTime = obs.obs_data_get_int(settings, timerName+"_start_time")
        self.endTime = obs.obs_data_get_int(settings, timerName+"_end_time")
        self.displaySource = obs.obs_data_get_string(settings, timerName+"_source")
        self.endText = obs.obs_data_get_string(settings, timerName+"_end_text")
        
        def start_timer(pressed):
            if pressed:
                if self.reset:
                    self.running = True
                    self.finished = False
                    self.sysStartTime = time.perf_counter()
                    self.lastUpdate = self.sysStartTime
                    self.elapsed = 0
                    self.reset = False
                    
                else:
                    self.running = True
                    self.finished = False
                    self.sysStartTime = self.sysStartTime + (time.perf_counter() - self.lastUpdate)
                
                obs.script_log(obs.LOG_INFO, "Start Timer")
                
                self.updateText()
            
        self.hotKeyStart = obs.obs_hotkey_register_frontend(timerName+"_start", "Start Timer "+timerName, start_timer)
        hotKeySaveArray = obs.obs_data_get_array(settings, timerName+"_start")
        obs.obs_hotkey_load(self.hotKeyStart, hotKeySaveArray)
        obs.obs_data_array_release(hotKeySaveArray)
        
        def stop_timer(pressed):
            if pressed:
                self.running = False
                self.finished = False

                if self.countDown:
                    if self.elapsed >= (self.startTime - self.endTime):                
                        self.running = False
                        self.finished = True
                        self.elapsed = (self.startTime - self.endTime)
                else:
                    if self.elapsed >= (self.endTime - self.startTime):                
                        self.running = False
                        self.finished = True
                        self.elapsed = (self.endTime - self.startTime)
                
                obs.script_log(obs.LOG_INFO, "Stop Timer")
                
                self.updateText()
            
        self.hotKeyStop = obs.obs_hotkey_register_frontend(timerName+"_stop", "Stop Timer "+timerName, stop_timer)
        hotKeySaveArray = obs.obs_data_get_array(settings, timerName+"_stop")
        obs.obs_hotkey_load(self.hotKeyStop, hotKeySaveArray)
        obs.obs_data_array_release(hotKeySaveArray)
        
        def reset_timer(pressed):
            if pressed:
                self.running = False
                self.finished = False
                self.reset = True
                self.elapsed = 0
                obs.script_log(obs.LOG_INFO, "Reset Timer")
                self.updateText()
            
        self.hotKeyReset = obs.obs_hotkey_register_frontend(timerName+"_reset", "Reset timer "+timerName, reset_timer)
        hotKeySaveArray = obs.obs_data_get_array(settings, timerName+"_reset")
        obs.obs_hotkey_load(self.hotKeyReset, hotKeySaveArray)
        obs.obs_data_array_release(hotKeySaveArray)
        
        return settings
    
    def updateText(self):

        settings = obs.obs_data_create()

        newVal = 0
        
        source = obs.obs_get_source_by_name(self.displaySource)
        if self.countDown:
            newVal = self.startTime - self.elapsed
        else:
            newVal = self.startTime + self.elapsed
        
        if self.finished:
            newVal = self.endText
            obs.obs_data_set_string(settings, "text", str(newVal))
        else:
            pad = ""
            newVal = locale.format_string("%."+str(self.placesAfter)+"f", newVal)
            numLen = newVal.index(sysDecimal)
            maxLen = self.placesBefore
            
            pad = "0" * (maxLen - numLen)
            obs.obs_data_set_string(settings, "text", pad+str(newVal))
        

        obs.obs_source_update(source, settings)
        obs.obs_data_release(settings)
        obs.obs_source_release(source)    
  

def script_tick(seconds):
    for timer in timers:
        if timer.running:
            timer.lastUpdate = time.perf_counter()
            timer.elapsed = (timer.lastUpdate - timer.sysStartTime)
            if timer.countDown:
                if timer.elapsed >= (timer.startTime - timer.endTime):                
                    timer.running = False
                    timer.finished = True
                    timer.elapsed = (timer.startTime - timer.endTime)
            else:
                if timer.elapsed >= (timer.endTime - timer.startTime):                
                    timer.running = False
                    timer.finished = True
                    timer.elapsed = (timer.endTime - timer.startTime)
        
        timer.updateText()
    
def script_load(settings):
    global totalTimers
    global sysDecimal
    locale.setlocale(locale.LC_ALL,'')
    
    sysDecimal = locale.localeconv()["decimal_point"]
    obs.script_log(obs.LOG_INFO, "sysDecimal: " + sysDecimal)
    
    totalTimers = obs.obs_data_get_int(settings,"total_timers")
    for prepTimer in range(totalTimers):
        timer = timerClass("timer_"+str(prepTimer+1))
        timers.append(timer)
        timers[prepTimer].loadTimer(settings, "timer_"+str(prepTimer+1))
        timers[prepTimer].updateText()
    obs.script_log(obs.LOG_INFO, "Timers script loaded")
    
def script_save(settings):
    for timer in timers:
        timer.saveTimer(settings)

def script_update(settings):
    global totalTimers
    oldTotal = totalTimers
    totalTimers = obs.obs_data_get_int(settings,"total_timers")
    
    for tmrNum in range(totalTimers):
        if tmrNum >= oldTotal:
            timer = timerClass("timer_"+str(tmrNum+1))
            timer.displaySource = obs.obs_data_get_string(settings, "timer_"+str(tmrNum+1)+"_source")
            timer.startTime = obs.obs_data_get_int(settings, "timer_"+str(tmrNum+1)+"_start_time")
            timer.endTime = obs.obs_data_get_int(settings, "timer_"+str(tmrNum+1)+"_end_time")
            timer.placesBefore = obs.obs_data_get_int(settings, "timer_"+str(tmrNum+1)+"_places_before")
            timer.placesAfter = obs.obs_data_get_int(settings, "timer_"+str(tmrNum+1)+"_places_after")
            if timer.startTime < timer.endTime:
                timer.countDown = False
            else:
                timer.countDown = True
            timers.append(timer)
            timer.updateText()
        else:
            timers[tmrNum].displaySource = obs.obs_data_get_string(settings, "timer_"+str(tmrNum+1)+"_source")
            timers[tmrNum].startTime = obs.obs_data_get_int(settings, "timer_"+str(tmrNum+1)+"_start_time")
            timers[tmrNum].endTime = obs.obs_data_get_int(settings, "timer_"+str(tmrNum+1)+"_end_time")
            timers[tmrNum].placesBefore = obs.obs_data_get_int(settings, "timer_"+str(tmrNum+1)+"_places_before")
            timers[tmrNum].placesAfter = obs.obs_data_get_int(settings, "timer_"+str(tmrNum+1)+"_places_after")
            if timers[tmrNum].startTime < timers[tmrNum].endTime:
                timers[tmrNum].countDown = False
            else:
                timers[tmrNum].countDown = True
            timers[tmrNum].updateText()
    

def script_properties():
    global totalTimers
    
    props = obs.obs_properties_create()
    obs.obs_properties_add_int(props, "total_timers", "Total Number of timers", 1, 2147483647, 1)
    
    for makeTimer in range(totalTimers):
    
        p = obs.obs_properties_add_list(props, "timer_"+str(makeTimer+1)+"_source", "Text Source to display Timer "+str(makeTimer+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_float(props, "timer_"+str(makeTimer+1)+"_start_time", "Timer Start Value", 0, 2147483647, 1)
        obs.obs_properties_add_float(props, "timer_"+str(makeTimer+1)+"_end_time", "Timer End Value", 0, 2147483647, 1)
        obs.obs_properties_add_text(props, "timer_"+str(makeTimer+1)+"_end_text", "Text to display at end of timer",  obs.OBS_TEXT_DEFAULT)
        
        obs.obs_properties_add_int(props, "timer_"+str(makeTimer+1)+"_places_before", "Digits Before the .", 0, 2147483647, 1)
        obs.obs_properties_add_int(props, "timer_"+str(makeTimer+1)+"_places_after", "Digits After the .", 0, 2147483647, 1)
    
    return props
        
def script_description():
    return "Timer system that can handle many timers. \n Press the script refresh button after changing timer count. \n You can also hit refresh after changing other options force an update. \n Timer will go whatever direction is needed to go from start to finish."