# ===============================================================================
#
#  Copyright (c) 2013-2016 Qualcomm Technologies, Inc.
#  All Rights Reserved.
#  Confidential and Proprietary - Qualcomm Technologies, Inc.
#
# ===============================================================================

from sectools.common.parsegen.elf.header import ident
from sectools.common.parsegen.elf.header.machine import EM_STRING, EM_DESCRIPTION
from sectools.common.parsegen.elf.header.type import ET_STRING, ET_DESCRIPTION
from sectools.common.parsegen.elf.header.version import EV_STRING, EV_DESCRIPTION
from sectools.common.utils.c_misc import hex_addr
from sectools.common.utils.c_misc import properties_repr
from sectools.common.utils.struct_base import StructBase


class Elf_Ehdr_Common(StructBase):
    """ELF Common Header Class"""

    def _unpack_data_list(self, unpacked):
        self.e_ident_mag = unpacked[0]
        self.e_ident_class = unpacked[1]
        self.e_ident_data = unpacked[2]
        self.e_ident_ver = unpacked[3]
        self.e_ident_osabi = unpacked[4]
        self.e_ident_abiver = unpacked[5]
        self.e_ident_unused = unpacked[6]
        self.e_type = unpacked[7]
        self.e_machine = unpacked[8]
        self.e_version = unpacked[9]

    def _pack_data_list(self):
        return [self.e_ident_mag,
                self.e_ident_class,
                self.e_ident_data,
                self.e_ident_ver,
                self.e_ident_osabi,
                self.e_ident_abiver,
                self.e_ident_unused,
                self.e_type,
                self.e_machine,
                self.e_version,]

    @classmethod
    def get_format(self):
        return '4sBBBBB7sHHI'

    def validate(self):
        # Check that the magic is correct
        if self.e_ident_mag not in ident.ELFMAG_DESCRIPTION.keys():
            raise RuntimeError('Invalid magic in the ELF file: ' + self.e_ident_mag + '\n'
                               'This is not an elf file.')

        # Check the class is correct
        if self.e_ident_class not in ELF_HDRS.keys():
            raise RuntimeError('Invalid class in the ELF file: ' + self.e_ident_class)

    def _repr_properties(self):
        properties = [
                      (ident.ELFMAG_STRING, ident.ELFMAG_DESCRIPTION, self.e_ident_mag),
                      (ident.ELFCLASS_STRING, ident.ELFCLASS_DESCRIPTION, self.e_ident_class),
                      (ident.ELFDATA_STRING, ident.ELFDATA_DESCRIPTION, self.e_ident_data),
                      (ident.ELFVER_STRING, ident.ELFVER_DESCRIPTION, self.e_ident_ver),
                      (ident.ELFOSABI_STRING, ident.ELFOSABI_DESCRIPTION, self.e_ident_osabi),
                      (ident.ELFABIVER_STRING, ident.ELFABIVER_DESCRIPTION, self.e_ident_abiver),
                      (ET_STRING, ET_DESCRIPTION, self.e_type),
                      (EM_STRING, EM_DESCRIPTION, self.e_machine),
                      (EV_STRING, EV_DESCRIPTION, self.e_version),
                     ]
        return [(attr, desc.get(val, val)) for attr, desc, val in properties]

    def __repr__(self):
        return properties_repr(self._repr_properties())


class Elf32_Ehdr(Elf_Ehdr_Common):
    """ELF 32bit Header Class"""

    def _unpack_data_list(self, unpacked_data):
        Elf_Ehdr_Common._unpack_data_list(self, unpacked_data)
        self.e_entry = unpacked_data[10]
        self.e_phoff = unpacked_data[11]
        self.e_shoff = unpacked_data[12]
        self.e_flags = unpacked_data[13]
        self.e_ehsize = unpacked_data[14]
        self.e_phentsize = unpacked_data[15]
        self.e_phnum = unpacked_data[16]
        self.e_shentsize = unpacked_data[17]
        self.e_shnum = unpacked_data[18]
        self.e_shstrndx = unpacked_data[19]

    def _pack_data_list(self):
        return (Elf_Ehdr_Common._pack_data_list(self) +
                [self.e_entry,
                 self.e_phoff,
                 self.e_shoff,
                 self.e_flags,
                 self.e_ehsize,
                 self.e_phentsize,
                 self.e_phnum,
                 self.e_shentsize,
                 self.e_shnum,
                 self.e_shstrndx,])

    @classmethod
    def get_format(cls):
        return Elf_Ehdr_Common.get_format() + 'IIIIHHHHHH'

    def _repr_properties(self):
        properties = [
                        ('Entry address', hex_addr(self.e_entry)),
                        ('Program headers offset', hex_addr(self.e_phoff)),
                        ('Section headers offset', hex_addr(self.e_shoff)),
                        ('Flags', hex_addr(self.e_flags)),
                        ('ELF header size', self.e_ehsize),
                        ('Program headers size', self.e_phentsize),
                        ('Number of program headers', self.e_phnum),
                        ('Section headers size', self.e_shentsize),
                        ('Number of section headers', self.e_shnum),
                        ('String table section index', self.e_shstrndx),
                     ]
        return Elf_Ehdr_Common._repr_properties(self) + properties

class Elf64_Ehdr(Elf32_Ehdr):
    """ELF 64bit Header Class"""

    @classmethod
    def get_format(cls):
        return Elf_Ehdr_Common.get_format() + 'QQQIHHHHHH'


ELF_HDRS = {
                ident.ELFCLASS32 : Elf32_Ehdr,
                ident.ELFCLASS64 : Elf64_Ehdr,
           }


def get_ehdr(_class):
    header_class = ELF_HDRS.get(_class, None)
    if header_class is None:
        supported_classes = [str(key) + '-' + str(ident.ELFCLASS_DESCRIPTION[key]) for key in ELF_HDRS.keys()]
        raise RuntimeError('File contains invalid class: ' + str(_class) + '\n'
                           '    ' + 'Supported classes are: ' + str(supported_classes))
    return header_class

def extract_ehdr(data):
    # Ensure data is of common elf header length
    if len(data) < Elf_Ehdr_Common.get_size():
        raise RuntimeError('Data length "' + str(len(data)) +
                           '" is less than common Elf Header length "' +
                           Elf_Ehdr_Common.get_size() + '"')

    # Get the appropriate elf header class
    header = Elf_Ehdr_Common(data)
    header_class = get_ehdr(header.e_ident_class)

    # Ensure data is of elf header length
    if len(data) < header_class.get_size():
        raise RuntimeError('Data length "' + str(len(data)) + '" is less than ' +
                           str(ident.ELFCLASS_DESCRIPTION[header.e_ident_class]) +
                           ' Elf Header length "' + header_class.get_size() + '"')

    # Extract the header
    header = header_class(data)
    return header

def create_empty_ehdr(_class):
    header_class = get_ehdr(_class)
    header = header_class()

    from sectools.common.parsegen.elf import header as hdr
    header.e_ident_mag = hdr.ident.ELFMAG
    header.e_ident_class = _class
    header.e_ident_data = hdr.ident.ELFDATA2LSB
    header.e_ident_ver = hdr.ident.ELFVERCURRENT
    header.e_ident_osabi = hdr.ident.ELFOSABI_NONE
    header.e_type = hdr.type.ET_NONE
    header.e_machine = hdr.machine.EM_ARM
    header.e_version = hdr.version.EV_CURRENT
    header.e_phoff = header.get_size()
    header.e_ehsize = header.get_size()

    header = header_class(header.pack())
    return header

