#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Decrypt and extract information files about a car.
Big thanks for aluigi@ZenHaxs.com that have the patience to help me to open the ACD files.

@author: albertowd
"""
import os, configparser, traceback, itertools, math
import codecs
from sys import exc_info
from collections import OrderedDict
from configparser import ConfigParser
from struct import unpack
if __name__ == '__main__':
    from ty_no_util import log
else:
    from ty_lt_util import log

def get_float(s):
  result=""
  for l in s.strip():
    if l.isnumeric() or l=='.' or l=='-':
      result=result+l
    else:
      break
  if result=="" or result=="-" or result==".":
    result="0"
  return float(result)

class ACD(object):
    """ Stores the ACD file contents. """

    def __init__(self, path):
        """ Default constructor receives the ACD file path. """
        self.path = path # only for real export
        path_v = path.split("/")

        # Initiate the class fields.
        self.__car = path_v[-1]
        self.__content = str()
        self.__content_size = len(self.__content)
        self.__files = OrderedDict()
        self.__key = generate_key(self.__car)
        # ac.log('++++++++++++++++++++++++\n' + self.__key)

        # Verify if the data.acd exists to load car information.
        data_acd_path = "{}/data.acd".format(path)
        if os.path.isfile(data_acd_path):
            log(self.__car + " loading from " + self.__car + "/data.acd ...")
            self.__load_from_file(data_acd_path)
        else:
            # If it don't, try to load from data folder.
            log(self.__car + " loading from data folder...")
            self.__load_from_folder("{}/data".format(path))

    def __load_from_file(self, path):
        """ Loads the car information by the data.acd encrypted file. """

        # Read all the file into memory.
        try:
            with open(path, "rb") as rb:
                self.__content = rb.read()
                self.__content_size = len(self.__content)
        except:
            log("Failed to open file {}:".format(path))
            for info in exc_info():
                log(info)

        if self.__content_size > 8:
            # Verify the "version" of the file.
            offset = 0
            dummy = unpack("l", self.__content[offset:offset + 4])[0]
            offset += 4
            if dummy < 0:
                # New cars, just pass the first 8 bytes.
                dummy = unpack("L", self.__content[offset:offset + 4])[0]
                offset += 4
            else:
                # Old cars don't have any version.
                offset = 0

            # Parse each inner file.
            while offset < self.__content_size:
                # Size of the file name.
                name_size = unpack("L", self.__content[offset:offset + 4])[0]
                offset += 4

                # File name.
                file_name = self.__content[offset:offset +
                                           name_size].decode("utf8")
                offset += name_size

                # File size.
                file_size = unpack("L", self.__content[offset:offset + 4])[0]
                offset += 4

                # Get the content and slices each 4 bytes.
                packed_content = self.__content[offset:offset + file_size * 4][::4]

                offset += file_size * 4

                # Decrypt the content of the file.
                decrypted_content = "" # bytearray()
                key_size = len(self.__key)

                for i in range(file_size):
                    code = packed_content[i] - ord(self.__key[i % key_size])
                    if code < 0: code = 256-abs(code)
                    # decrypted_content.append(ord(chr(code)))
                    decrypted_content += chr(code)

                # Save the decrypted file.
                self.set_file(decrypted_content, file_name)

                # Save the decrypted file on disk. data folder must exist
                #with codecs.open(self.path + '/data/' + file_name, 'wb') as w:
                #    w.write(decrypted_content)

        elif self.__content_size > 0:
            log("File too small to decrypt: {} bytes.".format(self.__content_size))

    def __load_from_folder(self, path):
        """ Loads the car information by the data folder. """
        for file_name in os.listdir(path):
            file_path = "{}/{}".format(path, file_name)
            if os.path.isfile(file_path):
                # log(file_name)
                try:
                    with open(file_path, 'r') as r:
                        self.set_file(r.read(), file_name)
                except:
                    log(' - warning - previous file skipped.')

    def __str__(self):
        """ Just print some useful information. """
        info  = "Car: {} - {} byte\n".format(self.__car, self.__content_size)
        info += "Key: {}\nFiles:\n".format(self.__key)
        for name in self.__files:
            info += "   {} - {}b\n".format(name, len(self.__files[name]))
        return info

    def check_file(self, name):
        """ Returns True if inner file exists. """
        return name in self.__files

    def get_file(self, name):
        """ Returns the content of an inner file. """
        if name in self.__files:
            lines1 = self.__files[name].encode('ascii', 'ignore').decode('ascii').strip()
            lines2 = ''
            for s1 in lines1.split('\n'):
                s2 = s1.strip()
                if s2!='' and s2[0]!=';' and s2[0]!='/' and s2[0]!='\\' and s2[0]!='#':
                    lines2 += s2+'\n'
                    #if '=' in s2:
                    #    sA=s2.split('=')
                    #    lines2 += sA+ '='+sA
                    #else:
                    #    lines2 += s2+'\n'
            lines1 = str(lines2).strip()
            return lines1
        else:
            return ""

    def set_file(self, content, name):
        """ Sets a new content to an inner file. """
        self.__files[name] = content

    #################################################

    def get_file_section_value(self, config, section, valuename, valueNameGiven="", valueGiven="", opt=""):
        res=""
        if valueGiven!="" and valueNameGiven!="":
            for i in range(10):
                sectname = "FRONT{}".format("" if i == 0 else "_{}".format(i))
                if config.has_option(sectname, valueNameGiven):
                    if config[sectname][valueNameGiven] == valueGiven:
                        sectname = section + "{}".format("" if i == 0 else "_{}".format(i))
                        if config.has_option(sectname, valuename):
                            res = get_float(config[sectname][valuename])
                            break
            return str(res)
        if config.has_section(section):
            if config.has_option(section,valuename):
                res = get_float(config[section][valuename])
        return str(res)

    # [THERMAL_FRONT]
    # PERFORMANCE_CURVE=tcurve_semis.lut				;File to use for temperature/grip relation
    # [FRONT]
    # NAME=Semislicks
    # SHORT_NAME=SM
    # WEAR_CURVE=semislicks_front.lut		; file with lookup table to call

    def get_file_section_value_as_curve(self, config, section, valuename, valueNameGiven="", valueGiven="", opt=""):
        res=[]
        if valueGiven!="" and valueNameGiven!="":
            for i in range(10):
                sectname = "FRONT{}".format("" if i == 0 else "_{}".format(i))
                if config.has_option(sectname, valueNameGiven):
                    if config[sectname][valueNameGiven] == valueGiven:
                        sectname = section + "{}".format("" if i == 0 else "_{}".format(i))
                        if config.has_option(sectname, valuename):
                            Curve = config[sectname][valuename]
                            if self.check_file(Curve):
                                # lut in file, get and parse values
                                # lines = self.get_file(Curve).split("\n")
                                # for i in range(len(lines)):
                                #     v=lines(i).split('|')
                                #     if len(v)>1:
                                #         lines[i] = lines[i].strip().replace("=","|")
                                # return lines
                                return self.get_file(Curve)
                            else:
                                if "|" in Curve:
                                    # inline lut
                                    lines = Curve.split("|")
                                    for i in range(len(lines)):
                                        lines[i] = lines[i].strip().replace("=","|")
                                    return lines
                                return Curve
            try:
                if len(config.sections)>0:
                    for s in config.sections:
                        print(s + ' - ' + section)
                        if s in section.lower(): # section
                            if config.has_option(s,valueNameGiven): # valuename
                                if config[s][valueNameGiven] == valueGiven: # value
                                    if config.has_option(s,valuename): #
                                        sect2 = s.replace(s,)
                                        return config[s][valuename]
            except:
                log('could not read [' + section + '] - ' + valuename)
            return res

        if config.has_section(section):
            if config.has_option(section,valuename):
                Curve = config[section][valuename]
                if self.check_file(Curve):
                    # lut in file
                    return self.get_file(Curve)
                else:
                    if "|" in Curve:
                        # inline lut
                        lines = Curve.split("|")
                        for i in range(len(lines)):
                            lines[i] = lines[i].strip().replace("=","|")
                        return lines
                    return Curve
        return []



    #################################################

    def get_ideal_pressure(self, compound, wheel):
        """ Returns the compound ideal pressure. """
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("tyres.ini"))
        try:
            if compound=="":
                compound = get_tire_name("", config, wheel)
            res = 0.0
            for s in config.sections():
                if 'FRONT' in s and wheel<2:
                    for name, value in config.items(s):
                        if (name=='short_name' or name=='name') and value==compound:
                            if config.has_option(s,"PRESSURE_IDEAL"):
                                return get_float(config[s]["PRESSURE_IDEAL"])
                if 'REAR' in s and wheel>1:
                    for name, value in config.items(s):
                        if (name=='short_name' or name=='name') and value==compound:
                            if config.has_option(s,"PRESSURE_IDEAL"):
                                return get_float(config[s]["PRESSURE_IDEAL"])
            return float(res)
        except:
            log("Failed to get tire ideal pressure: "+ name + "\n" + traceback.format_exc())
            #for info in exc_info():
            #    log(info)
            #raise
            return 0.0

    def get_FRICTION_LIMIT_ANGLE(self, compound, wheel):
        """ Returns the compound ideal pressure. """
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("tyres.ini"))
        try:
            if compound=="":
                compound = get_tire_name("", config, wheel)
            res = 5.0
            for s in config.sections():
                if 'FRONT' in s and wheel<2:
                    for name, value in config.items(s):
                        if (name=='short_name' or name=='name') and value==compound:
                            if config.has_option(s,"FRICTION_LIMIT_ANGLE"):
                                return get_float(config[s]["FRICTION_LIMIT_ANGLE"])
                if 'REAR' in s and wheel>1:
                    for name, value in config.items(s):
                        if (name=='short_name' or name=='name') and value==compound:
                            if config.has_option(s,"FRICTION_LIMIT_ANGLE"):
                                return get_float(config[s]["FRICTION_LIMIT_ANGLE"])
            return float(res)
        except:
            log("Failed to get tire ideal pressure: "+ name + "\n" + traceback.format_exc())
            #for info in exc_info():
            #    log(info)
            #raise
            return 5.0


    def getTyreWidthFront(self, compound):
        """ Returns the Front tyre width. """
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("tyres.ini").replace('%', 'mod') )
        try:
            name = get_tire_name(compound, config, 0)
            sect=''
            for s in config.sections():
                if 'FRONT' in s:
                    for name, value in config.items(s):
                        if (name=='short_name' or name=='name') and value==compound:
                            sect = s
                            break
                if sect!='':
                    break
            if sect!='':
                if config.has_option(sect, "WIDTH"):
                    return get_float(config[sect]["WIDTH"])
                else:
                    return 0.0
            else:
                return 0.0
        except:
            #ac.log('error: ' + traceback.format_exc() )
            log("Failed to get tire width:")
            for info in exc_info():
                log(info)
            raise

    def getTyreWidthRear(self, compound):
        """ Returns the Rear tyre width. """
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("tyres.ini"))
        try:
            name = get_tire_name(compound, config, 2)
            sect=''
            for s in config.sections():
                if 'REAR' in s:
                    for name, value in config.items(s):
                        if (name=='short_name' or name=='name') and value==compound:
                            sect = s
                            break
                if sect!='':
                    break
            if sect!='':
                if config.has_option(sect, "WIDTH"):
                    return get_float(config[sect]["WIDTH"])
                else:
                    return 0.0
            else:
                return 0.0
        except:
            #ac.log('error: ' + traceback.format_exc() )
            log("Failed to get tire width:")
            for info in exc_info():
                log(info)
            raise

    def getRimRadiusRear(self, compound):
        """ Returns the Rear rim radius. """
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("tyres.ini"))
        try:
            name = get_tire_name(compound, config, 2)
            sect=''
            for s in config.sections():
                if 'REAR' in s:
                    for name, value in config.items(s):
                        if (name=='short_name' or name=='name') and value==compound:
                            sect = s
                            break
                if sect!='':
                    break
            if sect!='':
                if config.has_option(sect, "RIM_RADIUS"):
                    return get_float(config[sect]["RIM_RADIUS"])
                else:
                    return 0.0
            else:
                return 0.0
        except:
            #ac.log('error: ' + traceback.format_exc() )
            log("Failed to get tire width:")
            for info in exc_info():
                log(info)
            raise

    def getRimRadiusFront(self, compound):
        """ Returns the Front rim radius. """
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("tyres.ini"))
        try:
            name = get_tire_name(compound, config, 0)
            sect=''
            for s in config.sections():
                if 'FRONT' in s:
                    for name, value in config.items(s):
                        if (name=='short_name' or name=='name') and value==compound:
                            sect = s
                            break
                if sect!='':
                    break
            if sect!='':
                if config.has_option(sect, "RIM_RADIUS"):
                    return get_float(config[sect]["RIM_RADIUS"])
                else:
                    return 0.0
            else:
                return 0.0
        except:
            #ac.log('error: ' + traceback.format_exc() )
            log("Failed to get tire width:")
            for info in exc_info():
                log(info)
            raise

    def getTyreRadiusFront(self, compound):
        """ Returns the Rear TYRE radius. """
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("tyres.ini"))
        try:
            name = get_tire_name(compound, config, 0)
            sect=''
            for s in config.sections():
                if 'FRONT' in s:
                    for name, value in config.items(s):
                        if (name=='short_name' or name=='name') and value==compound:
                            sect = s
                            break
                if sect!='':
                    break
            if sect!='':
                if config.has_option(sect, "RADIUS"):
                    return get_float(config[sect]["RADIUS"])
                else:
                    return 0.0
            else:
                return 0.0
        except:
            #ac.log('error: ' + traceback.format_exc() )
            log("Failed to get tire width:")
            for info in exc_info():
                log(info)
            raise

    def getTyreRadiusRear(self, compound):
        """ Returns the Rear TYRE radius. """
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("tyres.ini"))
        try:
            name = get_tire_name(compound, config, 0)
            sect=''
            for s in config.sections():
                if 'FRONT' in s:
                    for name, value in config.items(s):
                        if (name=='short_name' or name=='name') and value==compound:
                            sect = s
                            break
                if sect!='':
                    break
            if sect!='':
                if config.has_option(sect, "RADIUS"):
                    return get_float(config[sect]["RADIUS"])
                else:
                    return 0.0
            else:
                return 0.0
        except:
            #ac.log('error: ' + traceback.format_exc() )
            log("Failed to get tire width:")
            for info in exc_info():
                log(info)
            raise

    def get_power_curve(self):
        """ Returns the rpm x power curve. """
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("engine.ini"))
        try:
            if config.has_section("HEADER"):
                return self.get_file(config["HEADER"]["POWER_CURVE"])
            else:
                log('engine.ini has no [HEADER]!')
                return ""
        except:
            log("Failed to get rpm power curve")


    def get_rpm_upshift(self):
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("drivetrain.ini"))
        #'AUTO_SHIFTER', 'UP'
        #'AUTO_SHIFTER', 'DOWN'
        if config.has_option("AUTO_SHIFTER","UP"):
            return float(config["AUTO_SHIFTER"]["UP"])
        else:
            return self.get_rpm_limiter() / 2.0

    def get_rpm_downshift(self):
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("drivetrain.ini"))
        #'AUTO_SHIFTER', 'UP'
        #'AUTO_SHIFTER', 'DOWN'
        if config.has_option("AUTO_SHIFTER","DOWN"):
            return float(config["AUTO_SHIFTER"]["DOWN"])
        else:
            return self.get_rpm_limiter()/2

    def get_gearcount(self):
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("drivetrain.ini"))
        if config.has_option("GEARS","COUNT"):
            res = config["GEARS"]["COUNT"]
        else:
            res = "0"
        return int(res)

    def get_coast_torque(self):
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("engine.ini"))
        # [COAST_REF]
        # RPM=7500                        ; rev number reference
        # TORQUE=85                        ; engine braking torque value in Nm at rev number reference
        if config.has_option("COAST_REF", "TORQUE"):
            return float(config["COAST_REF"]["TORQUE"])
        return 0.0

    def get_turbo_count(self):
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("engine.ini"))
        cnt = 1
        for i in range(10):
            name = "TURBO{}".format("" if i == 0 else "_{}".format(i))
            if config.has_option(name, "MAX_BOOST"):
                cnt += 1
        return cnt

    def get_turbo(self):
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("engine.ini"))
        wastegate = 0.0
        cntw = 1
        for i in range(10):
            name = "TURBO{}".format("" if i == 0 else "_{}".format(i))
            if config.has_option(name, "MAX_BOOST"):
                if config.has_option(name, "WASTEGATE"):
                    # WASTEGATE=0.625                ; Max level of boost before the wastegate does its things. 0 = no wastegate#
                    wastegate += float(config[name]["WASTEGATE"])
                    cntw += 1
        #if cnt>0:
        #    turbo = turbo / float(cnt)
        #if wastegate>0.0:
        #    turbo = wastegate / float(cntw)
        # return turbo
        return wastegate

    def get_wastegate(self):
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("engine.ini"))
        turbo = 0.0
        wastegate = 0.0
        cnt = 1
        cntw = 1
        for i in range(10):
            name = "TURBO{}".format("" if i == 0 else "_{}".format(i))
            if config.has_option(name, "MAX_BOOST"):
                turbo += float(config[name]["MAX_BOOST"])
                cnt += 1
                if config.has_option(name, "WASTEGATE"):
                    # WASTEGATE=0.625                ; Max level of boost before the wastegate does its things. 0 = no wastegate#
                    wastegate += float(config[name]["WASTEGATE"])
                    cntw += 1
        #if cnt>0:
        #    turbo = turbo / float(cnt)
        #if wastegate>0.0:
        #    turbo = wastegate / float(cntw)
        # return turbo
        return wastegate

    def get_wastegate_display(self):
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("engine.ini"))
        turbo = 0.0
        cnt = 1
        for i in range(10):
            name = "TURBO{}".format("" if i == 0 else "_{}".format(i))
            if config.has_option(name, "DISPLAY_MAX_BOOST"):
                turbo += float(config[name]["DISPLAY_MAX_BOOST"])
        return turbo

    def get_ers_curve(self):
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("ers.ini"))
        if config.has_option("KERS","TORQUE_CURVE"):
            return self.get_file(config["KERS"]["TORQUE_CURVE"])
        elif config.has_option("KINETIC","TORQUE_CURVE"):
            return self.get_file(config["KINETIC"]["TORQUE_CURVE"])
        return ""

    def get_totalmass(self):
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("car.ini"))
        if config.has_option("BASIC","TOTALMASS"):
            res = config["BASIC"]["TOTALMASS"]
        else:
            res = float(0)
        return float(res)

    def get_carname(self):
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("car.ini"))
        # [INFO]
        # SCREEN_NAME=ACL BMW CSL 3.5
        # SHORT_NAME=CSL 3.5
        if config.has_option("INFO","SCREEN_NAME"):
            res = config["INFO"]["SCREEN_NAME"]
        else:
            res = ''
        return res

    def get_driveType(self):
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("drivetrain.ini"))
        if config.has_option("TRACTION","TYPE"):
            return config["TRACTION"]["TYPE"]
        else:
            return ""

    def get_brakeTorque(self):
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("brakes.ini"))
        #self.get_file("brakes.ini"):
        #    config.read_file(itertools.chain(['[HEADER]'], fp), source="brakes.ini")
        if config.has_option("DATA","MAX_TORQUE"):
            res = config["DATA"]["MAX_TORQUE"]
        else:
            res = "0"
        return float(res)

    def get_handbrakeTorque(self):
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("brakes.ini"))
        if config.has_option("DATA","HANDBRAKE_TORQUE"):
            res = config["DATA"]["HANDBRAKE_TORQUE"]
        else:
            res = 0.0
        return float(res)

    def get_rpm_limiter(self):
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("engine.ini"))
        #'ENGINE_DATA', 'LIMITER'
        if config.has_option("ENGINE_DATA","LIMITER"):
            return float(config["ENGINE_DATA"]["LIMITER"])
        else:
            return 0.0

    def get_rpm_damage(self):
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("engine.ini"))
        #'DAMAGE', 'RPM_THRESHOLD'
        if config.has_option("DAMAGE","RPM_THRESHOLD"):
            res = config["DAMAGE"]["RPM_THRESHOLD"]
            res = get_float(res)
        else:
            res = self.get_rpm_limiter()+100
        return float(res)

    def get_temp_curve(self, compound, wheel):
        """ Returns the compound temperature grip curve. """
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("tyres.ini"))
        name = "THERMAL_{}".format(get_tire_name(compound, config, wheel))
        if config.has_section(name):
            if config.has_option(name,"PERFORMANCE_CURVE"):
                Curve = config[name]["PERFORMANCE_CURVE"]
                if self.check_file(Curve):
                    return self.get_file(Curve)
                else:
                    if "|" in Curve:
                        lines = Curve.split("|")
                        for i in range(len(lines)):
                            lines[i] = lines[i].replace("=","|")
                        return lines
                    return Curve

#    lines = Split(s2, "|")
#    linesC = UBound(lines)
#    If linesC <= 0 Then: Exit Function
#    For i = 0 To linesC - 1
#        lines(i) = Replace(lines(i), "=", "|")
#    Next

        else:
            return []

    def get_wear_curve(self, compound, wheel):
        """ Returns the compound wear curve. """
        config = ConfigParser(empty_lines_in_values=False, inline_comment_prefixes=(";","/","#","\\","\t"), comment_prefixes=(";","/","#","\\","\t"), strict=False, allow_no_value=True)
        config.read_string("[ACHEADER]\n" + self.get_file("tyres.ini"))

        name = get_tire_name(compound, config, wheel)
        if config.has_section(name):
            if config.has_option(name,"WEAR_CURVE"):
                Curve = config[name]["WEAR_CURVE"]
                if self.check_file(Curve):
                    return self.get_file(Curve)
                else:
                    if "|" in Curve:
                        lines = Curve.split("|")
                        for i in range(len(lines)):
                            lines[i] = lines[i].replace("=","|")
                        return lines
                    return Curve
        else:
            return []

def generate_key(car_name):
    """ Generates the 8 values key from the car name. """
    i = 0
    key1 = 0
    while i < len(car_name):
        key1 += ord(car_name[i])
        i += 1
    key1 &= 0xff

    i = 0
    key2 = 0
    while i < len(car_name) - 1:
        key2 *= ord(car_name[i])
        i += 1
        key2 -= ord(car_name[i])
        i += 1
        #ac.log(str(key2) + ' - '+str(type(key2)))
    key2 &= 0xff
    # ac.log(str(key2) + ' - '+str(type(key2)))

    i = 1
    key3 = 0
    while i < len(car_name) - 3:
        key3 *= ord(car_name[i])
        i += 1
        key3 = int(key3 / (ord(car_name[i]) + 0x1b))
        i -= 2
        key3 += -0x1b - ord(car_name[i])
        i += 4
    key3 &= 0xff

    i = 1
    key4 = 0x1683
    while i < len(car_name):
        key4 -= ord(car_name[i])
        i += 1
    key4 &= 0xff

    i = 1
    key5 = 0x42
    while i < len(car_name) - 4:
        tmp = (ord(car_name[i]) + 0xf) * key5
        i -= 1
        key5 = (ord(car_name[i]) + 0xf) * tmp + 0x16
        i += 5
    key5 &= 0xff

    i = 0
    key6 = 0x65
    while i < len(car_name) - 2:
        key6 -= ord(car_name[i])
        i += 2
    key6 &= 0xff

    i = 0
    key7 = 0xab
    while i < len(car_name) - 2:
        key7 %= ord(car_name[i])
        i += 2
    key7 &= 0xff

    i = 0
    key8 = 0xab
    while i < len(car_name) - 1:
        key8 = int(key8 / ord(car_name[i])) + ord(car_name[i + 1])
        i += 1
    key8 &= 0xff

    return "{}-{}-{}-{}-{}-{}-{}-{}".format(key1, key2, key3, key4, key5, key6, key7, key8)

############################################################################################

def get_tire_id_max(config):
    prefix = "FRONT{}"
    prefixr = "REAR{}"
    max=0
    for i in range(10):
        name  = prefix.format("" if i == 0 else "_{}".format(i))
        namer = prefixr.format("" if i == 0 else "_{}".format(i))
        if config.has_option(name, "SHORT_NAME") and config.has_option(namer, "SHORT_NAME"):
            max += 1
    if max>0:
        return max
    i = 0
    if config.has_option("COMPOUND_DEFAULT","INDEX"):
        i = int(config["COMPOUND_DEFAULT"]["INDEX"])
        name = prefix.format("" if i == 0 else "_{}".format(i))
        namer = prefixr.format("" if i == 0 else "_{}".format(i))
        if config.has_option(name, "SHORT_NAME") and config.has_option(namer, "SHORT_NAME"):
            max += 1
    return max

def get_tire_id(compound, config):
    prefix = "FRONT{}"
    prefixr = "REAR{}"
    if compound!="":
        for i in range(10):
            name  = prefix.format("" if i == 0 else "_{}".format(i))
            namer = prefixr.format("" if i == 0 else "_{}".format(i))
            # Check if the SHORT_NAME index exists for tire backward compatibility.
            if config.has_option(name, "SHORT_NAME") and config[name]["SHORT_NAME"] == compound \
               and config.has_option(namer, "SHORT_NAME") and config[namer]["SHORT_NAME"] == compound:
                return i
    i = 0
    if config.has_option("COMPOUND_DEFAULT","INDEX"):
        i = int(config["COMPOUND_DEFAULT"]["INDEX"])
        name = prefix.format("" if i == 0 else "_{}".format(i))
        namer = prefixr.format("" if i == 0 else "_{}".format(i))
        if config.has_option(name, "SHORT_NAME") and config.has_option(namer, "SHORT_NAME"):
            return i
    return 0

def get_tire_name(compound, config, wheel):
    """ Returns the compound session name on the tire.ini configuration file. """
    prefix = "FRONT{}" if int(wheel)<2 else "REAR{}"
    if compound!="":
        for i in range(10):
            name = prefix.format("" if i == 0 else "_{}".format(i))
            # Check if the SHORT_NAME index exists for tire backward compatibility.
            if config.has_option(name, "SHORT_NAME") and config[name]["SHORT_NAME"] == compound:
                return config[name]["SHORT_NAME"]
    i = 0
    if config.has_option("COMPOUND_DEFAULT","INDEX"):
        i = int(config["COMPOUND_DEFAULT"]["INDEX"])
        name = prefix.format("" if i == 0 else "_{}".format(i))
        if config.has_option(name, "SHORT_NAME"):
            return config[name]["SHORT_NAME"]
    return ""

def get_tire_name_by_id(id, config):
    prefix = "FRONT{}"
    for i in range(10):
        name = prefix.format("" if i == 0 else "_{}".format(i))
        # Check if the SHORT_NAME index exists for tire backward compatibility.
        if config.has_option(name, "SHORT_NAME") and id == i:
            return config[name]["SHORT_NAME"]
    return ""

def get_tire_namelong(compound, config, wheel):
    """ Returns the compound session name on the tire.ini configuration file. """
    prefix = "FRONT{}" if int(wheel)<2 else "REAR{}"
    for i in range(10):
        name = prefix.format("" if i == 0 else "_{}".format(i))
        # Check if the SHORT_NAME index exists for tire backward compatibility.
        #log('acd: ' + name + ' ' + compound)
        if config.has_option(name, "NAME") and config.has_option(name, "SHORT_NAME") and config[name]["SHORT_NAME"] == compound:
            # log('acd:   ' + compound + ' - ' + config[name]["NAME"] )
            return config[name]["NAME"]
    i = 0
    #ac.log('prefix:   ' + prefix + ' ' + compound)
    if config.has_option("COMPOUND_DEFAULT","INDEX"):
        i = int(config["COMPOUND_DEFAULT"]["INDEX"])
        name = prefix.format("" if i == 0 else "_{}".format(i))
        if config.has_option(name, "NAME"):
            return config[name]["NAME"]
    return "-"   # prefix.format("" if i == 0 else "_{}".format(i))

def mapFromTo(val,a,b,x,y):
   return (val-a)/(b-a)*(y-x)+x

def get_val_from_lut(currvalue, lutLines1, lutLines2, flag=False):
    # Lines1, tmp, v1 [   0     20     40  currvalue    75      90       100        140       160       200]
    # Lines2, res, v2 [0.90   0.94   0.99          x   1.0     1.0     0.995      0.985      0.92      0.88]
    if len(lutLines1) == 0 or len(lutLines2) == 0: return 0
    currvalue = min(currvalue, max(lutLines1) )
    if not flag:
        tmp = float(lutLines1[0])
        res = float(lutLines2[0])
        for v1,v2 in zip(lutLines1,lutLines2):
            if float(v1) >= currvalue:
                if float(v1) == currvalue:
                    res = float(v2)
                else:
                    if float(v1)!=0.0 and float(v2)!=0.0 and float(v1) - float(tmp)!=0.0 and float(v2) - float(res)!=0:
                        if v2!=0:
                            #res = float(res) + (float(v1) - float(currvalue)) / (float(v1) - float(tmp)) * ( float(v2) - float(res) )
                            res = mapFromTo(currvalue,tmp,v1,res,v2)
                break
            tmp = v1
            res = v2
    else:
        tmp = float(lutLines1[len(lutLines1)-1])
        res = float(lutLines2[len(lutLines2)-1])
        for v1,v2 in zip(lutLines1[::-1],lutLines2[::-1]): # reverse
            if float(v1) >= currvalue:
                if float(v1) == currvalue:
                    res = float(v2)
                else:
                    if float(v1)!=0.0 and float(v2)!=0.0 and float(tmp)!=0.0 and float(res)!=0.0 and float(v1) - float(tmp)!=0.0 and float(v2) - float(res)!=0.0:
                        if v2!=0:
                            # res = float(v2) - abs(float(v1) - float(currvalue)) / abs(float(v1) - float(tmp)) * ( float(v2) - float(res) )
                            res = mapFromTo(currvalue,tmp,v1,v2,res)
                break
            tmp = v1
            res = v2
    return res

