#!/usr/bin/env python3
#
# Copyright (C) 2020-2021 Savoir-faire Linux Inc.
#
# Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
#
# This program 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.
#
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Creates packaging targets for a distribution and architecture.
# This helps reduce the length of the top Makefile.
#

import os
import sys
import shutil
import typing
import argparse
import platform
import subprocess
from zipfile import ZipFile

from sdkConstants import OS_IDS


def getSystem():
    system = platform.system().lower()
    if system == "linux" or system == "linux2":
        return OS_IDS["Linux"]
    elif system == "darwin":
        return OS_IDS["Darwin"]
    elif system == "windows":
        return OS_IDS["Windows"]
    sys.exit("Plugins SDK not supported on this system")


def getJpls():
    filePaths = input("\nInput jpls path you want to merge:\n")
    filePaths = filePaths.replace(' ', ',').replace(",,", ',')
    filePaths = filePaths.split(',')
    if (len(filePaths) > 0):
        print("\nJpl files to merge:")
    for filePath in filePaths:
        print(filePath)
    return filePaths


def checkValidityJpls(filePaths):
    for filePath in filePaths:
        if (not os.path.exists(filePath) or not filePath.endswith(".jpl")):
            return False
    return True


class JPLStructure:
    def __init__(self, paths, output = "", is_merge = True):
        self.paths = paths
        self.outputName = output
        self.pluginNames = []
        self.ZipObjcts: typing.Dict[str, list[ZipFile]] = {}
        if is_merge:
            self.getOutName()
        self.discoverJPLs()
        if is_merge:
            self.mergeJPLs()

    def getOutName(self):
        while (not self.outputName or not self.outputName.endswith(".jpl")):
            self.outputName = input("\nWhere to save the resulting JPL? ")
        self.OutObj = ZipFile(self.outputName, 'w')

    def discoverJPLs(self):
        for path in self.paths:
            name = os.path.split(path)[0].split('.')[0]
            self.pluginNames.append(name)
            self.ZipObjcts[path] = []
            self.ZipObjcts[path].append(ZipFile(path, 'r'))
        return

    def mergeJPLs(self):
        self.fileNames = []
        for path in self.paths:
            [obj] = self.ZipObjcts[path]
            for item in obj.filelist:
                if item.filename not in self.fileNames:
                    self.OutObj.writestr(item, obj.open(item).read())
                    self.fileNames.append(item.filename)
        self.OutObj.close()


def onerror(func, path, exc_info):
    """
    Error handler for ``shutil.rmtree``.

    If the error is due to an access error (read only file)
    it attempts to add write permission and then retries.

    If the error is for another reason it re-raises the error.

    Usage : ``shutil.rmtree(path, onerror=onerror)``
    """
    import stat
    if not os.access(path, os.W_OK):
        # Is the error an access error ?
        os.chmod(path, stat.S_IWUSR)
        func(path)
    else:
        raise


def preAssemble(pluginId, distribution='', arch=''):
    localSystem = getSystem()

    osBuildPath = "build-local"
    if localSystem == OS_IDS["Linux"]:
        if (distribution != 'android'):
            distribution = "x86_64-linux-gnu"
    elif localSystem == OS_IDS["Darwin"]:
        distribution = f"{arch}-apple-Darwin"
    elif localSystem == OS_IDS["Windows"]:
        distribution = "x64-windows"
        osBuildPath = "msvc"

    chDir = False
    if (osBuildPath in os.getcwd()):
        chDir = True
        os.chdir("./../")
    if (os.path.exists(f"./../{pluginId}/{osBuildPath}/")):
        if (os.path.exists(f"./../{pluginId}/{osBuildPath}/jpl")):
            shutil.rmtree(f"./../{pluginId}/{osBuildPath}/jpl", onerror=onerror)
    else:
        os.mkdir(f"./../{pluginId}/{osBuildPath}")
    os.mkdir(f"./../{pluginId}/{osBuildPath}/jpl")
    os.mkdir(f"./../{pluginId}/{osBuildPath}/jpl/lib")
    if (distribution != 'android'):
        os.mkdir(f"./../{pluginId}/{osBuildPath}/jpl/lib/{distribution}")
    else:
        if ("ANDROID_ABI" in os.environ.keys()):
            for abi in os.environ["ANDROID_ABI"].split(' '):
                os.mkdir(f"./../{pluginId}/{osBuildPath}/jpl/lib/{abi}")

    shutil.copytree(f"./../{pluginId}/data/",
                    f"./../{pluginId}/{osBuildPath}/jpl/data/")
    shutil.copyfile(f"./../{pluginId}/manifest.json",
                    f"./../{pluginId}/{osBuildPath}/jpl/manifest.json")
    if (os.path.exists(f"./../{pluginId}/data/preferences.json")):
        shutil.copyfile(
            f"./../{pluginId}/data/preferences.json",
            f"./../{pluginId}/{osBuildPath}/jpl/data/preferences.json")
    if (chDir):
        os.chdir(f"./{osBuildPath}")


def assemble(pluginId, extraPath='', distribution='', arch=''):
    extraPath = '/' + extraPath
    localSystem = getSystem()
    root = os.path.dirname(os.path.abspath(__file__)) + "/.."
    osBuildPath = "build-local"

    if localSystem == OS_IDS["Linux"]:
        if (distribution != 'android'):
            distribution = "x86_64-linux-gnu"
    elif localSystem == OS_IDS["Darwin"]:
        distribution = f"{arch}-apple-Darwin"
    elif localSystem == OS_IDS["Windows"]:
        distribution = "x64-windows"
        osBuildPath = 'msvc'
    if (not os.path.exists(f"{root}/build")):
        os.mkdir(f"{root}/build")
    if (not os.path.exists(f"{root}/build/{distribution}")):
        os.mkdir(f"{root}/build/{distribution}")
    if (not os.path.exists(f"{root}/build/{distribution}{extraPath}")):
        os.mkdir(f"{root}/build/{distribution}{extraPath}")
    if (os.path.exists(f"./../build/{pluginId}.jpl")):
        os.remove(f"./../build/{pluginId}.jpl")

    outputJPL = f"{root}/build/{distribution}{extraPath}/{pluginId}.jpl"
    outputBuild = f"{root}/{pluginId}/{osBuildPath}/jpl"

    with ZipFile(outputJPL, 'w') as zipObj:
        for folderName, subfolders, filenames in os.walk(outputBuild):
            for filename in filenames:
                filePath = os.path.join(folderName, filename)
                zipObj.write(
                    filePath, f"{folderName.split('/jpl')[-1]}/{filename}")
        zipObj.close()


def build(pluginId):
    currentDir = os.getcwd()
    os.chdir('./../')
    subprocess.run([
        sys.executable, os.path.join(
            os.getcwd(), "build-plugin.py"),
        "--projects", pluginId
    ], check=True)
    os.chdir(currentDir)


def parser():
    parser = argparse.ArgumentParser(description='Build some plugins.')
    parser.add_argument('--plugin', type=str,
                        help='Name of plugin to be built')
    parser.add_argument('--extraPath', type=str, default="",
                        help='output intermediate Path')
    parser.add_argument('--distribution', type=str, default='',
                        help="GNU/Linux or Windows, leave empty. Android, type android")
    parser.add_argument('--arch', type=str,
                        help='Specify the architecture (x86_64, arm64).')

    # to build or not to build
    parser.add_argument('--build', action='store_true')

    # to preassemble or not
    parser.add_argument('--preassemble', action='store_true')

    # to assemble jpl or not
    parser.add_argument('--assemble', action='store_true')

    # to assemble jpl or not
    parser.add_argument('--merge', action='store_true')

    # to merge two or more jpl
    parser.add_argument('--path', nargs='*', required=False,
                        help="Jpl full path")
    parser.add_argument('--output', type=str, default='',
                        help="Jpl output full path")

    args = parser.parse_args()
    return args


def main():
    args = parser()

    if args.preassemble:
        preAssemble(args.plugin, args.distribution, args.arch)
    if (args.build):
        build(args.plugin)
    if (args.assemble):
        assemble(args.plugin, args.extraPath, args.distribution, args.arch)
    if (args.merge):
        JPLStructure(args.path, args.output)


if __name__ == '__main__':
    main()