#!/usr/bin/env python3

import os
import argparse
import re
import shutil
import subprocess
from time import gmtime, strftime


# At this point we have all the required data: bundle directory and its location
# in the filesystem, along with the binary program file to be used as a starting
# point for all the framework stuff.

def createBundleSubDirs(dirDic):

    macosDir = dirDic["bundleDirAbsPathName"] + "/Contents/MacOS";
    if not os.path.exists(macosDir):
        os.mkdir(macosDir);
        print("Created " + macosDir + "\n");
    dirDic["macosDir"] = macosDir;
    
    frameworksDir = dirDic["bundleDirAbsPathName"] + "/Contents/Frameworks";
    if not os.path.exists(frameworksDir):
        os.mkdir(frameworksDir);
        print("Created " + frameworksDir + "\n");
    dirDic["frameworksDir"] = frameworksDir; 

    pluginsDir = dirDic["bundleDirAbsPathName"] + "/Contents/Plugins";
    dirDic["pluginsDir"] = pluginsDir; 
    copyPlugins(dirDic)
    # if not os.path.exists(pluginsDir):
        # os.mkdir(pluginsDir);
        # print("Created " + pluginsDir + "\n");
    
    platformsDir = pluginsDir + "/platforms";
    # In this platforms directory we'll later copy the libqcocoa.dylib (see below)
    if not os.path.exists(platformsDir):
        os.mkdir(platformsDir);
        print("Created " + platformsDir + "\n");
    dirDic["platformsDir"] = platformsDir;
    
    sqldriversDir = pluginsDir + "/sqldrivers";
    # In this sqldrivers directory we'll later copy the libqsqlite.dylib (see below)
    if not os.path.exists(sqldriversDir):
        os.mkdir(sqldriversDir);
        print("Created " + sqldriversDir + "\n");
    dirDic["sqldriversDir"] = sqldriversDir;
    
    resourcesDir = dirDic["bundleDirAbsPathName"] + "/Contents/Resources";
    if not os.path.exists(resourcesDir):
        os.mkdir(resourcesDir);
        print("Created " + resourcesDir + "\n");
    dirDic["resourcesDir"] = resourcesDir;
    

def copyPlugins(dirDic):
    ''' We want to copy in the app bundle all the plugins that ensure the proper
    skinning of the program in MacOSX.'''

    source_plugin_dir = "/opt/local/libexec/qt5/plugins";
    pluginsDir = dirDic["bundleDirAbsPathName"] + "/Contents/Plugins";
    sub_dir_list = dirDic["pluginsSubDirList"]

    for sub_dir in sub_dir_list:
        source_dir = source_plugin_dir + "/" + sub_dir
        dest_dir = pluginsDir + "/" + sub_dir
        shutil.copytree(source_dir, dest_dir)

        # Now append the file names to the list of files 
        # in the plugins directory. Note that we want the full path of 
        # each file and we thus need to recreate it.

        file_list = os.listdir(dest_dir)

        for iter_file in file_list:
            dirDic["pluginsFileList"].append(dest_dir + "/" + iter_file)

    # Now that the work has been done, print out the whole list of files
    # in the Plugins directory.

    print("List of all the files in the Plugins directory: " + str(dirDic["pluginsFileList"]))


def nonSystemDepLibs(binaryImage):
    if not os.path.exists(binaryImage):
        print("Could not find " + binaryImage + " .Exiting");
        exit(1);

    # Use the otool utility to check for the dependencies of binaryImage. This
    # tool is roughly like using objdump -p <binaryImage> | grep NEEDED on
    # GNU/Linux.

    completedProcess = subprocess.run(["otool", "-L", binaryImage], stdout=subprocess.PIPE)
    completedProcess.check_returncode();
    
    output = completedProcess.stdout.decode("utf-8");
    # print(output);

    # The ouptut for otool -L XtpCpp:

   # (ins)temporaire3:MacOS rusconi$ otool -L XtpCpp 
   # XtpCpp:
        # @executable_path/../Frameworks/libomp.dylib (compatibility version 5.0.0, current version 5.0.0)
        # @executable_path/../Frameworks/QtXml (compatibility version 5.13.0, current version 5.13.2)
        # @executable_path/../Frameworks/QtSvg (compatibility version 5.13.0, current version 5.13.2)
        # @executable_path/../Frameworks/QtPrintSupport (compatibility version 5.13.0, current version 5.13.2)
        # /Users/rusconi/devel/qcustomplot/build-area/mac/libqcustomplot.2.0.dylib (compatibility version 2.0.0, current version 2.0.1)
        # @rpath/libIsoSpec++.2.dylib (compatibility version 2.0.0, current version 2.1.0)
        # @rpath/libpappsomspp.0.dylib (compatibility version 0.0.0, current version 0.7.10)
        # @rpath/libpappsomspp-widget.0.dylib (compatibility version 0.0.0, current version 0.7.10)
        # @executable_path/../Frameworks/QtWidgets (compatibility version 5.13.0, current version 5.13.2)
        # @executable_path/../Frameworks/QtGui (compatibility version 5.13.0, current version 5.13.2)
        # @executable_path/../Frameworks/QtCore (compatibility version 5.13.0, current version 5.13.2)
        # /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 400.9.0)
        # /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.50.4)

    
    # The various dependency libraries are all contained in:
    #
    # /usr/local/lib or # /opt/local/lib or /Users/rusconi/devel/project_dir
    # directories or @rpath/.
    
    lines = output.split("\n");
    # print(lines);
    libDependencies = [ ];
    
    for line in lines:
        # print(line + "\n");

        # Now extract only the library path (first string between first space
        # and second space
        match = re.match(r"^\s+([/@]\S+)\s*.*$", line)
        if match:
            # print(match.group(1));
            matching_string = match.group(1)
            # strip the matching string
            matching_string = "".join(matching_string.split())
            print("Stripped matching_string: " + matching_string)

            # Store the dep lib absolute path only if the lib is 
            # not a system library.
            if matching_string.startswith("/Users"):
                libDependencies.append(match.group(1));
            if matching_string.startswith("/opt/local"):
                libDependencies.append(match.group(1));
        
    #print("All the local/lib dependencies of the binary " + binaryImage + ": \n");
    #print('\n'.join(libDependencies));
       
    return libDependencies;


def copyLibsToBundle(libList, frameworksDir, overwrite = False):

    if not os.path.exists(frameworksDir):
        print("Could not find " + frameworksDir + " .Exiting");
        exit(1);

    copiedFilesList = [];

    for lib in libList:
        if not os.path.exists(frameworksDir + "/" + os.path.basename(lib)) or overwrite:
            shutil.copy2(lib, frameworksDir);
            copiedFilesList.append(lib);

    print("List of copied libraries:\n" + "\n".join(copiedFilesList) + "\n");

    completedProcess = subprocess.run(["ls", frameworksDir], stdout=subprocess.PIPE)
    completedProcess.check_returncode();
    output = completedProcess.stdout.decode("utf-8");

    print("\nThe list of frameworks copied to the bundle:\n" + output);


def doInstallNameDashI(bin_object):

    print("install_name_tool -id bin_object: " + bin_object + "\n");

    if not os.path.exists(bin_object):
        print("doInstallNameDashI: could not find " + bin_object + " .Exiting");
        exit(1);

    bin_object_base_name = os.path.basename(bin_object)

    string1 = "@executable_path/../Frameworks/" + bin_object_base_name;
    string2 = bin_object;

    completedProcess = subprocess.run(["install_name_tool",
        "-id", string1, string2], stdout=subprocess.PIPE)
    completedProcess.check_returncode();
    output = completedProcess.stdout.decode("utf-8");
    # print(output);


def doInstallNameDashChange(bin_object):

    if not os.path.exists(bin_object):
        print("doInstallNameDashChange: could not find " + bin_object + " .Exiting");
        exit(1);

    print("install_name_tool -change bin_object: " + bin_object + "\n");
    
    # returns the libraries that bin_object depends on as
    # full path.
    depLibs = nonSystemDepLibs(bin_object);

    print("Dep libs:\n" + " ".join(depLibs) + "\n");

    # For each dependee library, make its name change inside the bin_object.
    for depLib in depLibs:

        print("Sub-processing dependee lib: " + depLib + "for bin_object" +
            bin_object + "\n");

        baseName = os.path.basename(depLib);

        new_name = "@executable_path/../Frameworks/" + baseName
        
        commandLine = ["install_name_tool", "-change", depLib, new_name, bin_object];
        print("Command line:\n" + " ".join(commandLine) + "\n");

        completedProcess = subprocess.run(["install_name_tool",
            "-change", depLib, new_name, bin_object], stdout=subprocess.PIPE)
        completedProcess.check_returncode();
        output = completedProcess.stdout.decode("utf-8");
        # print(output);


def getProgramVersionFromCMakeLists(dirDic):
    configFilePath = dirDic["configFilePath"]

    with open(configFilePath, 'r') as file:
        config_string = file.read()
    
    # Now search for the set(VERSION 6.0.3)
    #match = re.search('set\(VERSION\s+"([0-9]{1,2}\.]{0,5})\)"', config_string)
    match = re.search('set\(VERSION\s+([0-9]+\.[0-9]+\.[0-9]+)\)', config_string)
    if match:
        version = match.group(1)
        print("The VERSION: " + version + "\n")
        return version


def doProgramSpecific(dirDic):
    
    # First the generic stuff for both programs:
    if not os.path.exists(dirDic["bundleDirAbsPathName"] + "/Contents/doc"):
      os.mkdir(dirDic["bundleDirAbsPathName"] + "/Contents/doc");

    shutil.copy2(dirDic["devDirName"] + "/LICENSE",
        dirDic["bundleDirAbsPathName"] + "/Contents/doc");
    shutil.copy2(dirDic["devDirName"] + "/doc/history.html",
        dirDic["bundleDirAbsPathName"] + "/Contents/doc");

    if(dirDic["programName"] == "XtpCpp"):
        dodoXtpCPP(dirDic);


def dodoXtpCPP(dirDic):

    doc_dir = dirDic["bundleDirAbsPathName"] + "/Contents/doc";

    print("Copy the XtpCpp PDF user manual to the bundle\n");
    shutil.copy2(dirDic["devDirName"] + "/doc/user-manual/minexpert2-doc.pdf",
        doc_dir);
    
    print("Copy the XtpCpp HTML user manual to the bundle\n");
    source_dir = dirDic["devDirName"] + "/doc/user-manual/build/minexpert2-user-manual/html/minexpert2-user-manual"
    dest_dir = doc_dir + "/html"
    shutil.copytree(source_dir, dest_dir)


def backupLibraries(libList):

    print("All backupLibs:\n");
    print(str(libList));
    
    allLibs = libList[0].split(" ");
    print("allLibs\n");
    print(allLibs);
    print("done allLibs\n");
    
    for lib in libList:
    
        # print("Making a backup of library: " + lib + "\n");
        baseName = os.path.basename(lib);
        dirName = os.path.dirname(lib);
        backupDirName = dirName + "/backup";
    
        if not os.path.exists(backupDirName):
            os.mkdir(backupDirName);
            print("Creating " + backupDirName);
            if not os.path.exists(backupDirName):
                print("Could not create " + backupDirName + " Exiting.\n");
                exit(1);
        
        bkpLibName = backupDirName + "/" + baseName;
        
        command = "shutil.copy2({lib}, {bkpLibName})".format(lib = lib,
                bkpLibName = bkpLibName);
        print(command);
    
        newPath = shutil.copy2(lib, bkpLibName);
        if newPath != bkpLibName:
            print("Failed to backup " + lib + "\n");
            exit(1);
    
    print("\nDone making backup of all the libraries\n");
    

def renameLibraries(libList, dirDic):

    # print("\nRename the libraries in list:\n");
    # print(libList);
    # print("\n");

    # allLibs = libList[0].split(" ");

    # Open a text file with write bit set

    libRenamingScriptFile = dirDic["devDirName"] + "/libRenamingScript.sh";
    dirDic["libRenamingScriptFile"] = libRenamingScriptFile;

    f = open(libRenamingScriptFile , 'w')

    for lib in libList:

        baseName = os.path.basename(lib);
        dirName = os.path.dirname(lib);

        wasLibName = dirName + "/was-" + baseName;
        
        command = "mv -v {lib} {wasLibName}\n".format(lib = lib,
                wasLibName = wasLibName);

        f.write(command);

    f.close();

    print("\nDone scripting all the renaming calls in " 
            + libRenamingScriptFile
            + "\n");


def reinstateLibraries(libList, dirDic):
  
    # print("\nReinstate the libraries in list:\n");
    # print(libList);
    # print("\n");

    # allLibs = backupLibs[0].split(" ");

    libReinstatingScriptFile = dirDic["devDirName"] + "/libReinstatingScript.sh";
    dirDic["libReinstatingScriptFile"] = libReinstatingScriptFile;

    f = open(libReinstatingScriptFile , 'w')

    for lib in libList:

        baseName = os.path.basename(lib);
        dirName = os.path.dirname(lib);

        wasLibName = dirName + "/was-" + baseName;
        
        command = "mv -v {wasLibName} {lib}\n".format(lib = lib,
                wasLibName = wasLibName);

        f.write(command);

    f.close();

    print("\nDone scripting all the reinstating calls in " 
            + libReinstatingScriptFile
            + "\n");


def moveBundleToDesktop(dirDic):

    bundleDirAbsPathName = dirDic["bundleDirAbsPathName"];
    print("bundleDirAbsPathName: " + bundleDirAbsPathName);

    if not os.path.exists(bundleDirAbsPathName):
        print("The bundle directory does not exist.\n");

    homeDir = os.getenv('USERPROFILE') or os.getenv('HOME')
    if not os.path.exists(homeDir):
        print("Home directory not found.\n");

    print("homeDir: " + homeDir);

    desktopBundleDir = homeDir + "/Desktop/" + dirDic["bundleDir"];
    print("desktopBundleDir: " + desktopBundleDir);

    backupDir = desktopBundleDir + strftime("-backup-%Y%m%d-%Hh%Mm", gmtime());
    print("backupDir: " + backupDir);

    print("Copying bundle to " + desktopBundleDir + "\n");

    if os.path.exists(desktopBundleDir):
        print("Creating backup of preexising bundle.\n");
        shutil.move(desktopBundleDir, backupDir);

    newlyCreatedDir = shutil.copytree(bundleDirAbsPathName, desktopBundleDir);

    if newlyCreatedDir != desktopBundleDir:
        print("Failed to copy the bundle to the Desktop.\n");

    print("Copied " + bundleDirAbsPathName + " to " + desktopBundleDir);
    
    
    # In order for the DiskUtils program to create a dmg file with our app
    # inside it, we need to put the app bundle inside a directory

    containerDir = homeDir + "/Desktop/" + dirDic["bundleDir"]
    containerDir = containerDir + "-" + getProgramVersion(dirDic) + "-macos"

    os.mkdir(containerDir);
    if not os.path.exists(containerDir):
        print("Failed to create new directory: " + containerDir + "\n");
        exit(1)

    dest_dir = os.path.join(containerDir, os.path.basename(desktopBundleDir))
    shutil.copytree(desktopBundleDir, dest_dir)

    # Now prepare the command line for the dev to sign the app bundle.

    import subprocess

    command_list = ['codesign', '--deep', '--verbose',
        '--sign', '"listes.rusconi@laposte.net"', dest_dir]

    command_string = " ".join(command_list)

    # Write the command string to a script file for convenience.

    script_file_name = homeDir + "/Desktop/codesign-command.sh"

    print("Writing the code signing command script file to :" + script_file_name)

    with open(script_file_name, 'w') as the_file:
        the_file.write(command_string + "\n")

    import stat
    os.chmod(script_file_name, stat.S_IRWXU | stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)

    print("Issue the following command to sign the bundle:\n" + command_string + "\n")
    print("Or use the codesign-command.sh script on the Desktop\n")
    answer = input("Hit the RETURN key when done or Ctrl-C to stop the process")














    


