# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

import os
import sys
import hashlib
import shutil

SIMPLYGON_PATH = os.getenv('SIMPLYGON_9_PATH')
if not SIMPLYGON_PATH:
    raise Exception('Simplygon initialization error: SIMPLYGON_9_PATH not set')
else:
    SIMPLYGON_PATH = os.path.expandvars(SIMPLYGON_PATH)


class __HostInfo:
    def __init__(self):
        self._name: str = 'Python'
        self._version: str = '0.0'
    
    def get_name(self):
        return self._name
    
    def get_version(self) -> str:
        return self._version


class __UnknownInfo(__HostInfo):
    def __init__(self):
        super(self.__class__, self).__init__()
        self._name = 'Unknown (Python)'
        self._version = '0.0'


class __PythonInfo(__HostInfo):
    def __init__(self):
        super().__init__()
        try:
            self._version = '{}.{}'.format(sys.version_info.major, sys.version_info.minor)
        except:
            pass


class __BlenderInfo(__HostInfo):
    def __init__(self):
        super().__init__()
        self._name = 'Blender (Python)'
        try:
            import bpy
            (major_version, minor_version, _) = bpy.app.version
            self._version = '{}.{}'.format(major_version, minor_version)
        except:
            pass


class __MaxInfo(__HostInfo):
    def __init__(self):
        super().__init__()
        self._name = '3ds Max (Python)'
        try:
            import pymxs
            version = pymxs.runtime.maxVersion()
            if len(version) > 3:
                self._version = '{}'.format(version[7])
            else:
                self._version = '2018 or lower'
        except:
            pass


class __MayaInfo(__HostInfo):
    def __init__(self):
        super().__init__()
        self._name = 'Maya (Python)'
        try:
            import maya.cmds as cmds
            self._version = '{}'.format(cmds.about(v=True))
        except:
            pass


class __HoudiniInfo(__HostInfo):
    def __init__(self):
        super().__init__()
        self._name = 'Houdini (Python)'
        try:
            version = hou.applicationVersionString().split('.')
            self._version = '{}.{}'.format(version[0], version[1])
        except:
            pass


def __get_file_md5(file_path):
    if file_path is None:
        return None
    md5 = hashlib.md5()
    with open(file_path, 'rb') as f:
        md5.update(f.read())
    return md5.hexdigest()


def __copy_pyd_file_if_required(simplygon_python_path):
    curr_dir = os.path.abspath(os.path.dirname(__file__))
    source_pyd = None
    curr_pyd = None
    if not os.path.exists(simplygon_python_path):
        raise Exception('Failed to locate Simplygon folder {}'.format(simplygon_python_path))
    for file in os.listdir(simplygon_python_path):
        if file.endswith('.pyd'):
            source_pyd = os.path.join(simplygon_python_path, file)
            break
    source_pyd_md5 = __get_file_md5(source_pyd)
    if source_pyd_md5 is None:
        raise Exception('Error - No .pyd file found in {}\n'.format(simplygon_python_path))
    # Remove all .pyd files in curr_dir that does not have
    # a matching MD5 checksum
    for file in os.listdir(curr_dir):
        full_path = os.path.join(curr_dir, file)
        if file.endswith('.pyd') and __get_file_md5(full_path) == source_pyd_md5:
            curr_pyd = full_path
        elif file.endswith('.pyd'):
            try:
                os.unlink(full_path)
            except IOError as err:
                raise Exception('Failed to remove old .pyd file!\nPlease make sure no other program is currently using Simplygon and try again.')
    # If curr_pyd is still None we need to copy over the correct one
    if curr_pyd is None:
        try:
            shutil.copy2(source_pyd, curr_dir)
        except IOError as err:
            raise Exception('Failed to copy new .pyd file!\nPlease make sure no other program is currently using Simplygon and try again.')


def __get_host():
    exe = os.path.basename(sys.executable).lower()
    if exe == 'python.exe':
        return __PythonInfo()
    elif 'Blender Foundation' in sys.executable:
        return __BlenderInfo()
    elif exe == '3dsmax.exe' or exe == '3dsmaxbatch.exe':
        return __MaxInfo()
    elif exe == 'maya.exe' or exe == 'mayapy.exe':
        return __MayaInfo()
    elif exe.find('houdini') > -1 and exe[-4:] == '.exe':
        return __HoudiniInfo()
    elif exe == 'hars.exe':
        return __HoudiniInfo()
    return __UnknownInfo()


def __setup_simplygon_module():
    host_info = __get_host()
    python_version = '{}.{}'.format(sys.version_info.major, sys.version_info.minor)

    # Load correct version of Simplygon depending on environment
    if sys.version_info.major >= 3:
        if host_info.get_name() == 'Blender':
            simplygon_python_path = os.path.join(SIMPLYGON_PATH, 'Python', 'Python-3.7_vs2015')
        else:
            simplygon_python_path = os.path.join(SIMPLYGON_PATH, 'Python', 'Python-{}'.format(python_version))    
    # Fallback to Simplygon for Python 2.7
    else:
        simplygon_python_path = os.path.join(SIMPLYGON_PATH, 'Python', 'Python-2.7_vs2015')

    # Copy correct pyd file to simplygon_loader.py's directory
    __copy_pyd_file_if_required(simplygon_python_path)

    return host_info


SIMPLYGON_HOST_INFO = __setup_simplygon_module()

# Finally - import Simplygon
from simplygon import Simplygon


def init_simplygon(simplygon_dll_path: str=None, license_data: str=None, verbose: bool=False) -> Simplygon.ISimplygon:
    """
    Initialize Simplygon
    """
    global SIMPLYGON_HOST_INFO
    if verbose:
        print('Initializing Simplygon...')

    # Allow overriding path to Simplygon DLL
    if simplygon_dll_path is None:
        simplygon_dll_path = os.path.join(SIMPLYGON_PATH, 'Simplygon.dll')

    simplygon = None
    # Allow overriding path to license file
    if license_data is not None:
        simplygon = Simplygon.InitializeSimplygon(simplygon_dll_path, license_data)
    else:
        simplygon = Simplygon.InitializeSimplygon(simplygon_dll_path)

    error = Simplygon.GetLastInitializationError()
    if error != 0:
        error_string = Simplygon.GetErrorString(error)
        sys.stderr.write('Initialization failed! {} (error {})\n'.format(error_string, error))
        return None

    if verbose:
        print('Initialization succeeded!')

    simplygon.SendTelemetry('Loader', SIMPLYGON_HOST_INFO.get_name(), SIMPLYGON_HOST_INFO.get_version(), '{}')

    return simplygon
