#!/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

based acd decryption in LiveTelemetry version 1.5.2
used with permission for proTyres

changed for proTyres
# 2.0.0 200602
"""

from collections import OrderedDict
from configparser import ConfigParser
from struct import unpack
from sys import exc_info
from os import path,listdir
from lib.proTyres_def import *
from lib.proTyres_util import ptLog


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

    def __init__(self, carname, acpath):
        """ Default constructor receives the ACD file path. """
        #path_v = acpath.split("/")

        # Initiate the class fields.
        self.__car = carname.lower() #path_v[-1]
        self.__content = bytearray()
        self.__content_size = len(self.__content)
        self.__files = OrderedDict()
        self.__key = generate_key(self.__car)

        # Verify if the data.acd exists to load car information.
        data_acd_path = "{}data.acd".format(acpath)
        if path.isfile(data_acd_path):
            #ptLog("proTyres: Loading from data.acd file",True)
            self.__load_from_file(data_acd_path)
        else:
            # If it don't, try to load from data folder.
            #ptLog("proTyres: Loading from ini&lut files",True)
            self.__load_from_folder(acpath)

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

        # Read all the file into memory.
        try:
            with open(acpath, "rb") as rb:
                self.__content = bytearray(rb.read())
                self.__content_size = len(self.__content)
        except:
            ptLog("proTyres: ERROR Failed to open '{}'".format(acpath),True)
            for info in exc_info():
                ptLog("proTyres: ERROR Exception '{}'".format(info),True)

        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:
                derr = False
                # 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
                #ptLog(file_name,True)

                # 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 = ""
                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 = 164
                        derr = True
                    decrypted_content += chr(code)

                # Save the decrypted file.
                self.set_file(decrypted_content, file_name, derr)
        else:
            ptLog("proTyres: ERROR File too small to decrypt: {} bytes".format(self.__content_size),True)

    def __load_from_folder(self, acpath):
        """ Loads the car information by the data folder. """
        for file_name in listdir(acpath):
            file_path = "{}{}".format(acpath, file_name)
            if path.isfile(file_path):
                try:
                    with open(file_path, "r") as r:
                        self.set_file(r.read(), file_name, False)
                except:
                    ptLog("proTyres: ERROR Failed to open file '{}'".format(file_name),True)
                    for info in exc_info():
                        ptLog("proTyres: ERROR Exception '{}'".format(info),True)

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

    def get_file(self, name):
        """ Returns the content of an inner file. """
        if name in self.__files:
            return self.__files[name]
        else:
            return ""
        
    def check_file(self, name):
        """ Returns true if in content """
        return name in self.__files

    def set_file(self, content, name, derr):
        """ Sets a new content to an inner file. """
        #only add tyres.ini and .lut files
        if name=='tyres.ini' or name.find('.lut')>0:
            if len(content)<1:
                ptLog("proTyres: WARNING Removed file, size is zero byte '{}'".format(name),True)
            else:
                if derr: ptLog("proTyres: WARNING Decrypt error in file '{}'".format(name),True)
                self.__files[name] = content

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
    key2 &= 0xff

    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)

