##########################################################
# proTyres Lib by Jens Roos (Wergilius at AC forum)
#
# 2.0.0 200529
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# For GNU General Public License see <http://www.gnu.org/licenses/>.
######################################################################

import ac
from acsys import CS
from os import path
from lib.proTyres_def import *
from lib.proTyres_util import Interpolate,InterpolateRGBA,ptLog
from lib.proTyres_cls import eolTable,gripTable,gripLevels
from lib.proTyres_dsp import changeEolHeight
from lib.proTyres_acd import ACD

def loadCompound(setup,cfg,tyre,tyreBox,appDebug,tcn):

  #Load car files
  dataacd = ACD(tyre.Car, setup.tyrePath)
  
  try:
    
    if dataacd.check_file("tyres.ini"):
      #parse car ini file
      #setup.tyre.read(setup.tyrePath+"tyres.ini")
      setup.tyre.read_string(dataacd.get_file("tyres.ini"))
    else:
      ptLog("proTyres: ERROR "+setup.tyrePath+"tyres.ini",True)
      ptLog("proTyres: ERROR ini file does not exist in any of the expected folders ("+tyre.Car+")",True)
      dataacd = None
      return
  except:
    ptLog("proTyres: ERROR "+setup.tyrePath+"tyres.ini",True)
    ptLog("proTyres: ERROR ini file has non valid characters or duplicate option name",True)
    dataacd = None
    return

  try:
    #check for use of supported _EXTENSTION parameters
    if not tyre.ExtInit:
      tyre.ExtInit = True
      if setup.tyre.has_section("_EXTENSION"):
        i = 0
        ptLog("proTyres: Reading CSP extension",cfg.logVerbose&STANDARD)
        if setup.tyre.has_option("_EXTENSION", "PRACTICAL_TEMP_RATIO"):
          tyre.RatioTempSurface = float(iniReturnString(setup.tyre.get("_EXTENSION", "PRACTICAL_TEMP_RATIO")+";"))
          tyre.RatioTempCore = 1-tyre.RatioTempSurface
          i += 1
        ptLog("proTyres: Supported parameters ("+str(i)+")",cfg.logVerbose&STANDARD)
        
    #search for tyre used in ini file
    f = "FRONT"
    r = "REAR"
    t = "THERMAL"
    idx=""
    tc="n/a"       
    i = 0
    #loop thru ini file, only first 9 element will be checked...
    while i<10:
      #does used tyre exist?
      if setup.tyre.has_section(f+idx):
        o=""
        #check that all sections valid
        if not setup.tyre.has_section(r+idx):
          o += r+idx+", "
        if not setup.tyre.has_section(t+"_"+f+idx):
          o += t+"_"+f+idx+", "
        if o!="":
          ptLog("proTyres: ERROR "+setup.tyrePath+"tyres.ini",True)
          ptLog("proTyres: ERROR "+o+"section is missing",True)         
          break

        o=""        
        #check that all options valid
        if not setup.tyre.has_option(f+idx, "SHORT_NAME"):
          o += f+idx+"->SHORT_NAME"+", "
        if not setup.tyre.has_option(f+idx, "NAME"):
          o += f+idx+"->NAME"+", "
        if not setup.tyre.has_option(f+idx, "PRESSURE_IDEAL"):
          o += f+idx+"->PRESSURE_IDEAL"+", "
        if not setup.tyre.has_option(r+idx, "PRESSURE_IDEAL"):
          o += r+idx+"->PRESSURE_IDEAL"+", "
        if not setup.tyre.has_option(t+"_"+f+idx, "PERFORMANCE_CURVE"):
          o += t+"_"+f+idx+"->PERFORMANCE_CURVE"+", "
        if not setup.tyre.has_option(t+"_"+r+idx, "PERFORMANCE_CURVE"):
          o += t+"_"+r+idx+"->PERFORMANCE_CURVE"+", "
        if not setup.tyre.has_option(f+idx, "WEAR_CURVE"):
          o += f+idx+"->WEAR_CURVE"+", "
        if not setup.tyre.has_option(r+idx, "WEAR_CURVE"):
          o += r+idx+"->WEAR_CURVE"+", "
        if o!="":
          ptLog("proTyres: ERROR "+setup.tyrePath+"tyres.ini",True)
          ptLog("proTyres: ERROR "+o+"option is missing",True)         
          break

        #check if found index is correct
        if (iniReturnString(setup.tyre.get(f+idx, "SHORT_NAME")+";")==tyre.CompoundShort) or (tyre.CompoundShort=="" and iniReturnString(setup.tyre.get(f+idx, "NAME")+";").lower()==tcn):
          #compound identified, set long name on debug window
          tc=iniReturnString(setup.tyre.get(f+idx, "NAME")+";")
          if appDebug.App and appDebug.lblCompound:
            ac.setText(appDebug.lblCompound, "COMPOUND: "+tc+" ("+tyre.CompoundShort+")")
          #get tyre data front and rear and lut file used for temp/grip
          tyre.IdealPressure[0] = int(setup.tyre.get(f+idx, "PRESSURE_IDEAL")[0:3])
          tyre.IdealPressure[1] = int(setup.tyre.get(r+idx, "PRESSURE_IDEAL")[0:3])
          if not cfg.DisableButton&CONFIG:
            for i in range(0,2):            
              ac.setRange(tyre.spinCfg[i],-tyre.IdealPressure[i]*0.5,tyre.IdealPressure[i]*0.5)
              #limit the range of the offset (if manualy changed ini)
              tyre.OffsetPressure[i]=min(max(tyre.OffsetPressure[i],-tyre.IdealPressure[i]*0.5),tyre.IdealPressure[i]*0.5)
              #save the original offset value for reference
              tyre.SavedOffset[i]=tyre.OffsetPressure[i]
          setup.tyreThermalFileF = iniReturnString(setup.tyre.get(t+"_"+f+idx, "PERFORMANCE_CURVE")+";")
          setup.tyreThermalFileR = iniReturnString(setup.tyre.get(t+"_"+r+idx, "PERFORMANCE_CURVE")+";")
          #check if lut is existing
          if dataacd.check_file(setup.tyreThermalFileF) and dataacd.check_file(setup.tyreThermalFileR):
    
            #init front temp/grip array
            fLevel = gripLevels()
            #create performance grip array and temp info 
            setGripArray(setup, tyre.Grip[0], fLevel, setup.tyreThermalFileF, dataacd)
            #assume single table
            tyre.GripFR = False
            #different index for front/rear is used
            x = False

            #check if rear have another filename
            if setup.tyreThermalFileF!=setup.tyreThermalFileR:

              #init rear temp/grip array
              rLevel = gripLevels()
              rGrip = gripTable()

              #create performance grip array and temp info 
              setGripArray(setup, rGrip, rLevel, setup.tyreThermalFileR, dataacd)

              #compare if curve is different
              for i in range(0, min(len(fLevel.Temp), len(rLevel.Temp))):
                #check if table is different
                if fLevel.Temp[i]!=rLevel.Temp[i] or fLevel.Grip[i]!=rLevel.Grip[i]:
                  x = True

              #if min/max is equal, check so behavior is consistent
              if tyre.Grip[0].MinTemp!=rGrip.MinTemp or tyre.Grip[0].MaxTemp!=rGrip.MaxTemp:
                tyre.GripFR = True

              #different index for front/rear                                
              if x:
                tyre.Grip[1] = rGrip
                tyreBox[2].GripIdx = 1
                tyreBox[3].GripIdx = 1
                #ac.console("thermal performance different front/rear")
              #else:
                #ac.console("thermal performance equal front/rear")

            #reset indexes for 2nd performance data
            if not x:
              tyre.Grip[1] = 0
              tyreBox[2].GripIdx = 0
              tyreBox[3].GripIdx = 0

            #create interpolated temp color array with reference to grip levels
            o = max(fLevel.Grip)
            if x:
              #if fron/rear, use highest grip (should normally be the same value, 100)
              o = max(o, max(rLevel.Grip))
            tyre.ActualTempColor = InterpolateRGBA([o-cfg.TempStep,o,o+cfg.TempStep], cfg.TempColorR, cfg.TempColorG, cfg.TempColorB, cfg.TempColorA)

            #create interpolated pressure color array with reference to optimal pressure ranges 
            tyre.ActualPressureColor = InterpolateRGBA([tyre.IdealPressure[0]-SCALE16,tyre.IdealPressure[0]-cfg.IdealPressureRange/2,tyre.IdealPressure[0],tyre.IdealPressure[0]+cfg.IdealPressureRange/2,tyre.IdealPressure[0]+SCALE16], cfg.PressureColorR, cfg.PressureColorG, cfg.PressureColorB, cfg.PressureColorA)

            #debug compound summary label      
            if appDebug.App and appDebug.lblTyre:
              if not tyre.GripFR:
                ac.setText(appDebug.lblTyre, "SETTINGS: PRESSURE FRONT {:.0f}psi, REAR {:.0f}psi, TEMP {:.0f}°C - {:.0f}°C".format(
                  tyre.IdealPressure[0],tyre.IdealPressure[1],tyre.Grip[0].MinTemp,tyre.Grip[0].MaxTemp))
              else:
                ac.setText(appDebug.lblTyre, "SETTINGS: PRESSURE FRONT {:.0f}psi, REAR {:.0f}psi, TEMP FRONT {:.0f}°C - {:.0f}°C, REAR {:.0f}°C - {:.0f}°C".format(
                  tyre.IdealPressure[0], tyre.IdealPressure[1],tyre.Grip[0].MinTemp,tyre.Grip[0].MaxTemp,tyre.Grip[1].MinTemp,tyre.Grip[1].MaxTemp))

            #check for the wear curve for eol estimation if enabled
            if cfg.useEOL:
              #add summary to info label
              if not tyre.GripFR:
                ac.setText(tyre.lblEolInfo[3], "Temp {:.0f} - {:.0f}°C".format(tyre.Grip[0].MinTemp, tyre.Grip[0].MaxTemp))
                #set standard textbox size due to double performance curves
                ac.setSize(tyre.lblEolInfo[3],cfg.FontSize*8,cfg.FontSize*1.5)
              else:
                ac.setText(tyre.lblEolInfo[3], "Temp {:.0f} - {:.0f}°C / {:.0f} - {:.0f}°C".format(
                  tyre.Grip[0].MinTemp, tyre.Grip[0].MaxTemp,tyre.Grip[1].MinTemp, tyre.Grip[1].MaxTemp))
                #set longer textbox size due to double performance curves
                ac.setSize(tyre.lblEolInfo[3],cfg.FontSize*12,cfg.FontSize*1.5)
                
              #set filenames
              setup.tyreEOLFileF = iniReturnString(setup.tyre.get(f+idx, "WEAR_CURVE")+";")
              setup.tyreEOLFileR = iniReturnString(setup.tyre.get(r+idx, "WEAR_CURVE")+";")
              #use end of life estimation, check for the file(s)
              if dataacd.check_file(setup.tyreEOLFileF) and dataacd.check_file(setup.tyreEOLFileR):
                #read the wear/eol from front lut table
                setEOLarray(setup, tyre.eol[0], setup.tyreEOLFileF, (cfg.WearLevels[0]),dataacd)
                
                #remove first post and reverse and interpolate list for km for each grip level
                o = Interpolate(tyre.eol[0].Grip[:0:-1],tyre.eol[0].Km[:0:-1])
                #assume single table setup
                tyre.eolFR = False
                #if front and rear have different table, check if they are different
                if setup.tyreEOLFileF!=setup.tyreEOLFileR:

                  #read wear in temporary variable
                  Reol = eolTable()
                  setEOLarray(setup, Reol, setup.tyreEOLFileR, (cfg.WearLevels[0]),dataacd)
                  #compare if curve is different
                  x = False
                  for i in range(0, min(len(tyre.eol[0].Km), len(Reol.Km))):
                    #check if table is different
                    if tyre.eol[0].Km[i]!=Reol.Km[i] or tyre.eol[0].Grip[i]!=Reol.Grip[i]:
                      x = True
                  #if front/rear table content or size is different
                  if x or len(tyre.eol[0].Km)!=len(Reol.Km):
                    #remove first post and reverse and interpolate list for km for each grip level
                    r = Interpolate(Reol.Grip[:0:-1],Reol.Km[:0:-1])
                    #check if km is different at 94% grip on front/rear lut table
                    #if table is different, force seperate tables
                    if r[cfg.WearLevels[0]*100]!=o[cfg.WearLevels[0]*100] or x:
                      #save rear wear cure and enable twin eol functions
                      tyre.eolFR = True
                      #class not initsiated 
                      if tyre.eol[1]==0:
                        tyre.eol[1] = Reol
                        tyre.eol[1].DynGainBuff = tyre.eol[0].DynGainBuff[:]
                        tyre.eol[1].Levels = tyre.eol[0].Levels[:]
                      #copy array values
                      tyre.eol[1].Km = Reol.Km[:]
                      tyre.eol[1].Grip = Reol.Grip[:]
                      tyre.eol[1].ActualGrip = Reol.ActualGrip
                      #if previous front gains should be set in case of unknown compound
                      if len(tyre.eol[1].DynGain)==0:
                        tyre.eol[1].DynGain = tyre.eol[0].DynGain[:]
                        tyre.eol[1].DynGainNew = tyre.eol[0].DynGainNew[:]
                      #get km levels with reference to wear levels, from 0-3 
                      for i in range(0,4):
                        tyre.eol[1].Levels[i] = r[cfg.WearLevels[i]*100]
                      #set longer size for info label due to double gain info
                      ac.setSize(tyre.lblEolInfo[4],cfg.FontSize*17,cfg.FontSize*1.5)
                #reset to single table, clear variables and reset info label
                if not tyre.eolFR:
                  tyre.eolIdx = 0
                  tyre.eol[1] = 0
                  ac.setSize(tyre.lblEolInfo[4],cfg.FontSize*8,cfg.FontSize*1.5)

                #set wear for new tyre 
                tyre.WearValue = tyre.eol[0].Grip[0]

                #get km levels with reference to wear levels, from 0-3
                for i in range(0,4):
                  tyre.eol[0].Levels[i] = o[cfg.WearLevels[i]*100]

                #make a copy for adjusted range using previous eol index
                tyre.eolLvlAdj = tyre.eol[tyre.eolIdx].Levels[:]

                #recalculate markers for eol markers
                #set info markers beside markes on bar
                #remember, pos is also set in function changeModeTyre
                changeEolHeight(cfg,tyre,True)  

              else:
                initDump(setup,tyre,cfg,tc,f,r,t,idx)
                ptLog("proTyres: ERROR '"+setup.tyrePath+setup.tyreEOLFileF+"'",True)
                #check if there is different front/rear wear files
                if setup.tyrePath+setup.tyreEOLFileF!=setup.tyrePath+setup.tyreEOLFileR:
                  ptLog("proTyres: ERROR '"+setup.tyrePath+setup.tyreEOLFileR+"'",True)
                ptLog("proTyres: ERROR Wear lut curve file(s) not found",True)
                break
              
            #set the compound short label
            if cfg.DisplayMode[0]&COMPACT+EXTEND!=COMPACT+EXTEND and cfg.DisplayMode[0]&COMPACT+EXTEND+NOWEAR!=EXTEND+NOWEAR:
              ac.setText(tyre.lblTyreCompound, tyre.CompoundShort[0:4])
            dc=""
            if tyre.eolFR or tyre.GripFR:
              dc=" ("
              if tyre.eolFR:
                dc+="w"
              if tyre.GripFR:
                dc+="p"
              dc+=")"
            #all parameters are valid 
            ptLog("proTyres: "+tc+" compound loaded ok"+dc,cfg.logVerbose&STANDARD)
            tyre.isValid = True
            break
          else:
            initDump(setup,tyre,cfg,tc,f,r,t,idx)
            ptLog("proTyres: ERROR Thermal lut curve file(s) not found",True)
          break
        else:
          #try next index
          i += 1
          idx = "_{}".format(i)
      else:
        initDump(setup,tyre,cfg,tc,f,r,t,idx)
        ptLog("proTyres: ERROR current compound '"+tyre.CompoundShort+"' not in tyres.ini",True)
        break
  except:
    initDump(setup,tyre,cfg,tc,f,r,t,idx)
    ptLog("proTyres: ERROR processing tyre compound data from settings, ini or lut files",True)

  dataacd = None

def setGripArray(setup, grip, lvl, tyreThermalFile, dataacd):
  #clear/init array
  lvl.Temp=[]
  lvl.Grip=[]
  #open lut performance curve and convert to list with grip and temp levels
  #with open(setup.tyrePath+tyreThermalFile) as lut: lines = [line.rstrip('\n') for line in lut]
  lines = dataacd.get_file(tyreThermalFile).split("\n")
  
  for line in lines:
    #some lut files has empty lines, ignore
    if (line.find('|')>-1) and (len(line.strip())>0):
      #ac.console(">>: "+line)
      #split the line and create array of the levels
      lvl.Temp.append(float(line[:line.find('|')]))
      lvl.Grip.append(float(line[line.find('|')+1:str(line+";").find(";")])*100)
  #get temp levels with the highest grip
  o = [i for i, j in enumerate(lvl.Grip) if j==max(lvl.Grip)]
  grip.MinTemp=lvl.Temp[o[0]]
  grip.MaxTemp=lvl.Temp[o[-1]]

  #check for single entry min/max optimal templevels
  if grip.MinTemp==grip.MaxTemp:
    #check that temp array is valid
    if o[0]-1>-1 and o[-1]+1<len(lvl.Temp):
      grip.MinTemp=lvl.Temp[o[0]-1]
      grip.MaxTemp=lvl.Temp[o[-1]+1]
    #not valid, set range around current level +- 2.5 degrees
    else:
      grip.MinTemp-=2.5
      grip.MaxTemp+=2.5

  #create interpolated griplevels arrays with referens to temp
  grip.Temp = Interpolate(lvl.Temp, lvl.Grip)


def setEOLarray(setup, eol, tyreEOLFile, lr, dataacd):
  #clear/init array
  rawKm=[]
  rawGrip=[]
  eolKm=[]
  eolGrip=[]
  eol.Km=[]
  eol.Grip=[]
  lr *= 100
  lr -= 1
  #open lut performance curve and convert to list with grip and temp levels
  #with open(setup.tyrePath+tyreEOLFile) as lut: lines = [line.rstrip('\n') for line in lut]
  lines = dataacd.get_file(tyreEOLFile).split("\n")
  
  for line in lines:
    #some lut files has empty lines, ignore
    if (line.find('|')>-1) and (len(line.strip())>0):
      #ac.console(">>: "+line)
      #split the line and create array of the levels
      rawKm.append(float(line[:line.find('|')])*10)
      rawGrip.append(float(line[line.find('|')+1:str(line+";").find(";")]))
  #check and remove post less than max after first post 
  y=0
  #get max grip
  o=max(rawGrip)
  for x in range(0, len(rawGrip)):
    #first post always ok and if found max, max is on rare times, only at first post
    if ((x>0) and rawGrip[x]==o) or rawGrip[0]==o:
      o = -1
    #add if post is valid, first and everything after first post with max
    if (x==y) or (o==-1):
      eolKm.append(rawKm[x])
      eolGrip.append(rawGrip[x])
  #check for very rare same km levels, remove double post with same km, keep last
  #return array to raw and reset
  rawKm=eolKm[:]
  rawGrip=eolGrip[:]
  eolKm=[]
  eolGrip=[]
  y=0
  o=[]
  for x in range(0, len(rawKm)):
    #first post always ok and skip if filtering
    if (x>0) and (x>y):
      #if any doubles, take last index
      o = [i for i, j in enumerate(rawKm) if j==rawKm[x]]
      y = o[-1]
    #add if post is valid or not in exclude list
    if (x==y) or ([i for i, j in enumerate(o) if j==x]==[]):
      eolKm.append(rawKm[x])
      eolGrip.append(rawGrip[x])
  #some rare wear files only have 100 wear at first entry and then only less values, i.e it starts with lower values at once
  if eolGrip[1]<eolGrip[0]:
    eolGrip.insert(1,eolGrip[0])
    eolKm.insert(1,eolKm[0])                  
  #check so range of grip is at least 93% to make sure whole eol range is coverd
  if eolGrip[-1]>lr:
    #wear is missing, add same km linear km as previous last 0.5% grip interpolated to 93% 
    eolKm.append((0.5/max((eolGrip[-2]-eolGrip[-1],0.25)))*max(eolKm[-1]-eolKm[-2],0.25)*((eolGrip[-1]-lr)/0.5)+eolKm[-1])
    eolGrip.append(lr)
      
  #interpolate to create grip with km as argument, set first grip to 2nd post,
  #keep all (to get correct return low gain luts), restore old value
  i = eolGrip[0]
  eolGrip[0]=eolGrip[1]
  #a few cars have 0km and 100 at first post, then
  if eolKm[0]==eolKm[1]:
    eol.ActualGrip = Interpolate(eolKm[1::],eolGrip[1::])
  else:  
    eol.ActualGrip = Interpolate(eolKm,eolGrip)
  eolGrip[0] = i
  #check for same grip levels, remove double post with same grip, keep last
  y=0
  o=[]
  for x in range(0, len(eolGrip)):
    #first post always ok and skip if filtering
    if (x>0) and (x>y):
      #if any doubles, take last index
      o = [i for i, j in enumerate(eolGrip) if j==eolGrip[x]]
      y = o[-1]
    #add if post is valid or not in exclude list
    if (x==y) or ([i for i, j in enumerate(o) if j==x]==[]):
      eol.Km.append(eolKm[x])
      eol.Grip.append(eolGrip[x])


#create summary dump for error handling information
def initDump(setup,tyre,cfg,tc,f,r,t,idx):
  ptLog("proTyres: CAR: "+tyre.Car,True)
  ptLog("proTyres: TYRE: "+tc+" ("+tyre.CompoundShort+")",True)
  ptLog("proTyres: INI: "+f+idx+", "+r+idx+", "+t+"_"+f+idx+", "+t+"_"+r+idx,True)
  ptLog("proTyres: CURVE: "+setup.tyrePath+setup.tyreThermalFileF,True)
  if setup.tyreThermalFileF!=setup.tyreThermalFileR:
    ptLog("proTyres: CURVE2: "+setup.tyrePath+setup.tyreThermalFileR,True)
  ptLog("proTyres: WEAR: "+setup.tyrePath+setup.tyreEOLFileF,True)
  if setup.tyreEOLFileF!=setup.tyreEOLFileR:
    ptLog("proTyres: WEAR2: "+setup.tyrePath+setup.tyreEOLFileR,True)

def getDeltaPos(pit):
  o=ac.getCarState(0, CS.WorldPosition)
  return abs(((o[0]-pit[0])**2+(o[1]-pit[1])**2+(o[2]-pit[2])**2)**0.5)

def getOrSetEOL(setup,cfg,tyre,tw,dist,rt,shutdown):
  #Initialize configparser on first attempt
  if tyre.Track=="":
    setup.eol.read(setup.eolPath)

  #check if track is going to be used to bound pressure offsets
  track=""
  if cfg.PressureFallback&TRACK:
    track=ac.getTrackName(0)[:20]+"_"
    if ac.getTrackConfiguration(0)!="":
      track += ac.getTrackConfiguration(0)[:10]+"_"

  #Check if config file has appropriate content, else add it
  if setup.eol.has_section(tyre.Car) != True:
      setup.eol.add_section(tyre.Car)
      #with open(setup.eolPath, 'w') as fileConfig:
      #  setup.eol.write(fileConfig)
      #  setup.eolUpdate = False

  #save old if value has been validated (i.e by using beyond new status)
  if tyre.Track!="" and setup.eolUpdate and tyre.isValid:
    #loop, if dual curve save 2nd gain after the first one
    k=""
    for idx in range(0,abs(tyre.eolFR)+1):
      o=False
      for i in range(0,7):
        #if not tuned, mark as autotune (-1.0)
        if tyre.eol[idx].DynGainNew[6-i]:
          #save first index that is not done, as it is
          if o:
            k="-1.0,"+k
          else:
            k=str(round(tyre.eol[idx].DynGain[7-i],8))+","+k
            o=True
        else:
          k=str(round(tyre.eol[idx].DynGain[7-i],8))+","+k
    #saved used index if double wear curves
    if tyre.eolFR:
      k+=str(tyre.eolIdx) 
    setup.eol.set(tyre.Car, tyre.Track, "["+k.rstrip(",")+"]")

  #save offsets if value has been changed (i.e by clicking on spinners)
  if tyre.Track!="" and tyre.isValid and setup.cfgOffsetUpdate and ((min(tyre.OffsetPressure)!=0 or max(tyre.OffsetPressure)!=0 or tyre.SavedRoadTemp!=0) or cfg.PressureOffset>49.9):
    #set changed offset and flag to save
    tyre.SavedRoadTemp =  int(round(round(rt/cfg.PressureOffset)*cfg.PressureOffset*10.0))
    for i in range(0,2):
      setup.eol.set(tyre.Car, track+tyre.CompoundOld+str(i)+"_"+str(tyre.SavedRoadTemp), str(int(tyre.OffsetPressure[i])))
    setup.cfgUpdate=True

  #if not shutdown, check for new tyre saved value or keep previous
  if not shutdown:
    
    #set key for this tyre, track and tyre wear combo
    dist = str(int(round(dist/1000)))
    tw = "0"+str(int(round(tw*10)))
    tyre.Track = tyre.CompoundShort+dist+tw[-2:]
    #ac.console(">>: "+tyre.Track)
    
    #check if previous value availible, if no value this tyre will overtake previous gain
    if setup.eol.has_option(tyre.Car, tyre.Track):
      o = [float(i) for i in setup.eol.get(tyre.Car, tyre.Track).lstrip("[").rstrip("]").split(",")]
      tyre.eol[0].DynGainNew = [False]*7
      #for old values, expand
      if len(o)==1:
        for i in range(0,6):
          o.insert(0,-1.0)
      tyre.eol[0].DynGainP = DYNGAIN_SLOW

      #set default index for non double curves
      k=6
      
      #tyre compound seems to be double lut curve
      if len(o)>7:
        #init the new eol arrays
        tyre.eolFR = True
        tyre.eol[1] = eolTable()
        tyre.eol[1].DynGain = tyre.eol[0].DynGain[:]
        tyre.eol[1].DynGainNew = [False]*7
        tyre.eol[1].DynGainBuff = tyre.eol[0].DynGainBuff[:]
        tyre.eol[1].Levels = tyre.eol[0].Levels[:]
        tyre.eolIdx = int(o[14])
        tyre.eol[1].DynGainP = DYNGAIN_SLOW
        k = 13

      #check for min/max range
      for idx in range(0,abs(tyre.eolFR)+1):
        for i in range(0,7):
          #check if -1, then enable autotune for index and set gain same as previous
          if i>0 and o[k-i]==-1.0:
            tyre.eol[idx].DynGainNew[6-i] = True
            o[k-i] = o[k-i+1]
          #enable also index before first that is done
          if i>0 and tyre.eol[idx].DynGainNew[6-i] and not tyre.eol[idx].DynGainNew[6-i+1]:
            tyre.eol[idx].DynGainNew[6-i+1] = True
          #limit gain settings 10% to 175%
          tyre.eol[idx].DynGain[7-i] = min(max(o[k-i],0.10),1.75)
        k = 6

    else:
      #if previous data or new data is used, set autotune sense to fast
      tyre.eol[0].DynGainP = DYNGAIN_FAST
      if tyre.eolFR:
        tyre.eol[1].DynGainP = DYNGAIN_FAST

    #reset gain for new tyre
    tyre.eol[0].DynGain[0] = tyre.eol[0].DynGain[-1]
    if tyre.eolFR:
      tyre.eol[1].DynGain[0] = tyre.eol[1].DynGain[-1]

    #check for save offsets if function is enabled
    if not cfg.DisableButton&CONFIG:
      o =  int(round(round(rt/cfg.PressureOffset)*cfg.PressureOffset*10.0))
      #scan for close offsets if enabled and an offset do not exist
      if cfg.PressureFallback&TEMP and not (setup.eol.has_option(tyre.Car, track+tyre.CompoundShort+str(0)+"_"+str(o))):
        rtsd=999
        ct=tyre.CompoundShort.lower()+str(0)+"_"
        #if only fallback on current track
        if cfg.PressureFallback&TRACK_ONLY:
          ct = track+ct
        p=setup.eol.items(tyre.Car)
        ti=len(ct)
        #scan thru all items
        for i in range(0, len(p)):
          tc=p[i][0].find(ct)
          #check if correct compound
          if tc>-1 and p[i][0][tc:tc+ti]==ct:
            #get the temp as save offset
            rts=int(p[i][0][tc+ti:])
            #save temp closest to current track temp
            if abs(rts-o)<abs(rtsd-o):
              rtsd=rts
              #if not track only, get track name (if any)
              if cfg.PressureFallback&TRACK and not cfg.PressureFallback&TRACK_ONLY:
                track=p[i][0][:tc]
        o=rtsd
      for i in range(0,2):
        #read pressure offset otherwise set to zero
        if setup.eol.has_option(tyre.Car, track+tyre.CompoundShort+str(i)+"_"+str(o)):
          #read the offset, do not limit here since valid range not always loaded
          tyre.OffsetPressure[i] = float(setup.eol.getint(tyre.Car, track+tyre.CompoundShort+str(i)+"_"+str(o)))
          tyre.SavedRoadTemp = o
        else:
          #if not fallback, reset reference temp and offset
          if not cfg.PressureFallback:
            tyre.OffsetPressure[i] = 0.0
            tyre.SavedRoadTemp = 0
      setup.cfgOffsetUpdate=False
      #offsets found and saved
      if tyre.SavedRoadTemp==o and (tyre.OffsetPressure[0]!=0 or tyre.OffsetPressure[1]!=0):
        ptLog("proTyres: Pressure offset F={}psi R={}psi Temp Reference={}°C".format(tyre.OffsetPressure[0],tyre.OffsetPressure[1],o/10.0),cfg.logVerbose&STANDARD)

  elif setup.eolUpdate or setup.cfgUpdate:
    #when shuting down, save the changes if a valid gain has been set
    with open(setup.eolPath, 'w') as fileConfig:
      setup.eol.write(fileConfig)

def setPitPos(tyre,pit,bp,id,vl):
  if pit==1 and (sum(tyre.PosPit)==0 or bp):
    tyre.PosPit = ac.getCarState(0, CS.WorldPosition)
    #ptLog("proTyres: PitBox has been set X:{} Y:{} Z:{}".format(tyre.Pit[0],tyre.Pit[1],tyre.Pit[2]))
    ptLog("proTyres: PitBox has been set ("+id+")",vl)

def setSessionPos(tyre,id,vl):
  tyre.PosSession = ac.getCarState(0, CS.WorldPosition)
  #ptLog("proTyres: PitBox has been set X:{} Y:{} Z:{}".format(tyre.Pit[0],tyre.Pit[1],tyre.Pit[2]))
  ptLog("proTyres: Session position set ("+id+")",vl)
    
#return a string without tabs, everything after ; (mandatory)is truncated
def iniReturnString(value):
  return value.rstrip('\t')[:value.rstrip('\t').find(';')].strip()

#return format float 1 decimal, 5 placeholders i.e 100.0
def fmt51(value):
  return "{:.1f}".format(value).zfill(5)

#return format float 1 decimal, 4 placeholders i.e 10.0
def fmt41(value):
  return "{:.1f}".format(value).zfill(4)

#wear format 0-100 based on 100-94% (100-91% if extend low) wear
def fmtWear(lvl,wear):
  return "{:.1f}".format((1.0-min(max(((wear/100)-lvl[0])/(lvl[3]-lvl[0]),0.0),1.0))*100)

#Load the boolean value or set it to default
def getOrSetDefaultBoolean(setup, key, opt, default):
  if setup.config.has_option(key, opt):
    return setup.config.getboolean(key, opt)
  else:
    setup.config.set(key, opt, str(default))
    setup.configUpdate = True
    return default

#Load the float value or set it to default
def getOrSetDefaultFloat(setup, key, opt, default):
  if setup.config.has_option(key, opt):
    return setup.config.getfloat(key, opt)
  else:
    setup.config.set(key, opt, str(default))
    setup.configUpdate = True
    return default

#Load the string value or set it to default
def getOrSetDefaultString(setup, key, opt, default):
  if setup.config.has_option(key, opt):
    return setup.config.get(key, opt).lower()
  else:
    setup.config.set(key, opt, default)
    setup.configUpdate = True
    return default.lower()
  
#Check and delete old key, send existing value if any
def getAndDeleteDefaultString(setup, key, opt):
  txt=setup.config.get(key, opt)
  setup.config.remove_option(key, opt)
  return txt


