#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Webcamoid, webcam capture application.
# Copyright (C) 2017  Gonzalo Exequiel Pedone
#
# Webcamoid 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.
#
# Webcamoid 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.
#
# You should have received a copy of the GNU General Public License
# along with Webcamoid. If not, see <http://www.gnu.org/licenses/>.
#
# Web-Site: http://webcamoid.github.io/

import mimetypes
import os
import struct
import sys

import tools.binary


class DeployToolsBinary(tools.binary.DeployToolsBinary):
    def __init__(self):
        super().__init__()

    def isValid(self, path):
        mimetype, encoding = mimetypes.guess_type(path)

        if mimetype == 'application/x-msdownload':
            return True

        if mimetype != 'application/octet-stream':
            return False

        with open(path, 'rb') as f:
            if f.read(2) != b'MZ':
                return []

            f.seek(0x3c, os.SEEK_SET)
            peHeaderOffset = struct.unpack('I', f.read(4))
            f.seek(peHeaderOffset[0], os.SEEK_SET)
            peSignatue = f.read(4)

            if peSignatue != b'PE\x00\x00':
                return False

        return True

    # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680547(v=vs.85).aspx
    # https://upload.wikimedia.org/wikipedia/commons/1/1b/Portable_Executable_32_bit_Structure_in_SVG_fixed.svg
    def dump(self, binary):
        dllImports = set()

        if not os.path.exists(binary) or not os.path.isfile(binary):
            return dllImports

        with open(binary, 'rb') as f:
            # Check DOS header signature.
            if f.read(2) != b'MZ':
                return []

            # Move to COFF header.
            f.seek(0x3c, os.SEEK_SET)
            peHeaderOffset = struct.unpack('I', f.read(4))
            f.seek(peHeaderOffset[0], os.SEEK_SET)
            peSignatue = f.read(4)

            # Check COFF header signature.
            if peSignatue != b'PE\x00\x00':
                return []

            # Read COFF header.
            coffHeader = struct.unpack('HHIIIHH', f.read(20))
            nSections = coffHeader[1]
            sectionTablePos = coffHeader[5] + f.tell()

            # Read magic signature in standard COFF fields.
            peType = 'PE32' if f.read(2) == b'\x0b\x01' else 'PE32+'

            # Move to data directories.
            f.seek(102 if peType == 'PE32' else 118, os.SEEK_CUR)

            # Read the import table.
            importTablePos, importTableSize = struct.unpack('II', f.read(8))

            # Move to Sections table.
            f.seek(sectionTablePos, os.SEEK_SET)
            sections = []
            idataTableVirtual = -1
            idataTablePhysical = -1

            # Search for 'idata' section.
            for i in range(nSections):
                # Read section.
                section = struct.unpack('8pIIIIIIHHI', f.read(40))
                sectionName = section[0].replace(b'\x00', b'')

                # Save a reference to the sections.
                sections += [section]

                if sectionName == b'idata' or sectionName == b'rdata':
                    idataTableVirtual = section[2]
                    idataTablePhysical = section[4]

                    # If import table was defined calculate it's position in
                    # the file in relation to the address given by 'idata'.
                    if importTableSize > 0:
                        idataTablePhysical += importTablePos - section[2]

            if idataTablePhysical < 0:
                return []

            # Move to 'idata' section.
            f.seek(idataTablePhysical, os.SEEK_SET)
            dllList = set()

            # Read 'idata' directory table.
            while True:
                # Read DLL entries.
                try:
                    dllImport = struct.unpack('IIIII', f.read(20))
                except:
                    break

                # Null directory entry.
                if dllImport[0] | dllImport[1] | dllImport[2] | dllImport[3] | dllImport[4] == 0:
                    break

                # Locate where is located the DLL name in relation to the
                # sections.
                for section in sections:
                    if dllImport[3] >= section[2] \
                        and dllImport[3] < section[1] + section[2]:
                        dllList.add(dllImport[3] - section[2] + section[4])

                        break

            for dll in dllList:
                # Move to DLL name.
                f.seek(dll, os.SEEK_SET)
                dllName = b''

                # Read string until null character.
                while True:
                    c = f.read(1)

                    if c == b'\x00':
                        break

                    dllName += c

                try:
                    dllImports.add(dllName.decode(sys.getdefaultencoding()))
                except:
                    pass

        return dllImports

    def dependencies(self, binary):
        deps = []

        for dep in self.dump(binary):
            depPath = self.whereBin(dep)

            if len(depPath) > 0 and not self.isExcluded(depPath):
                deps.append(depPath)

        return deps

    def name(self, binary):
        dep = os.path.basename(binary)

        return dep[: dep.find('.')]
