#!/usr/bin/env python3
#
# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018  Rickard Lindberg, Roger Lindberg
#
# This file is part of Timeline.
#
# Timeline 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.
#
# Timeline 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 Timeline.  If not, see <http://www.gnu.org/licenses/>.


"""
This script is used to create a Timeline installation file for Windows

Usage:
    Copy this file to a temporary directory and issue the command
        python build_win_setup_executable.py [version]

    If a version argument is given, it must be the name of a tagged version
    such as 2.10.0.
    If not given, the latest code is used.

    The path to the python version used is defined by the pragram constant
    PYTHON_HOME. If default isn't what you want you have to change it.

Prerequisite:
    1. Must run on a Windows machine
    2. Mercurial (hg) must be installed
    3. Python must be installed
    4. Inno Setup (iscc.exe) must be installed
    5. Dependent on files in Timeline repo:
        tools/execute-specs.py
        tools/generate-mo-files.py
        tools/winbuildtools/mod_paths.py
        tools/winbuildtools/mod_iss_timeline_version.py
        tools/winbuildtools/timeline.spec
        tools/winbuildtools/dist/icons/*
        tools/winbuildtools/dist/translations/*


"""

import os
import subprocess
from cmdlineparser import CmdLineParser, PositionalArg, NamedArg


class BuildError(Exception):
    pass


class Build:

    def __init__(self, python_home, build_version=None, keep_workspace=False):
        self.PYTHON = os.path.join(python_home, "python.exe")
        self.PIP = os.path.join(python_home, "pip.exe")
        self.PYINSTALLER = None
        self._build_version = build_version
        self._root_dir = os.path.dirname(os.path.realpath(__file__))
        self._keep_workspace = keep_workspace
        self._log_header("Input data")
        self._log(f"Python home   : {python_home}")
        self._log(f"Build version : {self._build_version}")
        self._log(f"Keep workspace: {self._keep_workspace}")

    def _validate_prerequisite(self):
        def os_is_windows():
            if os.name == "nt":
                self._log("OS is windows")
            else:
                raise BuildError("This script only runs on Windows!")

        def mercurial_is_installed():
            if self._os_command("where", "hg").endswith("hg.exe"):
                self._log("Mercurial is installed")
            else:
                raise BuildError("Mercurial is not installed!")

        def python_installed():
            try:
                version = self._os_command(self.PYTHON, "-V")
                self._log(f"Python {version} is installed")
            except:
                raise BuildError("Python is not installed!")

        def iscc_is_installed():
            if self._os_command("where", "iscc").lower().endswith("iscc.exe"):
                self._log("ISCC is installed")
            else:
                raise BuildError("ISCC is not installed!")

        self._log_header("Validating Prerequisite")
        os_is_windows()
        mercurial_is_installed()
        python_installed()
        iscc_is_installed()

    def _create_workspace(self):
        self._create_dir("workspace")
        self._cd("workspace")

    def _os_command(self, *args):
        return subprocess.check_output(list(args)).decode("latin-1").strip()

    def _create_dir(self, dirname):
        os.mkdir(dirname)

    def _cd(self, dirname):
        if dirname == "root":
            path = self._root_dir
        else:
            path = os.path.join(self._root_dir, dirname)
        os.chdir(path)
        self._log(f"Directory changed to: {os.getcwd()}")

    def _remove_dir(self, dirname):
        os.rmdir(dirname)

    def _get_timeline_from_repo(self):
        """http://hg.code.sf.net/p/thetimelineproj/main"""
        self._log_header("Clone Timeline repo")
        os.system("hg clone http://hg.code.sf.net/p/thetimelineproj/main .")
        self._log_header("Get hg info")
        self._hg_node = self._get_hg_node()
        self._build_version_exists()
        self._hg_rev = self._get_hg_rev()
        self._log(f"Build version: {self._build_version}")
        self._log(f"Node         : {self._hg_node}")
        self._log(f"Revision     : {self._hg_rev}")

    def _build_version_exists(self):
        if self._build_version is not None:
            try:
                self._os_command(
                    "hg", "log", "--rev", self._build_version, "--template", "exists"
                )
                self._log("Build version exists")
                self._os_command("hg", "update", "--rev", self._build_version)
                self._log("Project updated to build version")
            except:
                raise BuildError(f"unknown revision: {self._build_version}!")

    def _get_hg_node(self):
        return self._os_command("hg", "log", "--rev", ".", "--template", "{node}")

    def _get_hg_rev(self):
        return self._os_command("hg", "log", "--rev", ".", "--template", "{rev}")

    def _pip_install(self, package, version=None):
        self._log(f'Install {package} {version if version is not None else ""}')
        if version is not None:
            self._os_command(self.PIP, "install", "-v", f"{package}=={version}")
        else:
            self._os_command(self.PIP, "install", package)

    def _create_virtual_environment(self):
        def create_venv():
            self._os_command(self.PYTHON, "-m", "venv", "venv")
            self.PYTHON = os.path.join(
                self._root_dir, "workspace", "venv", "Scripts", "python.exe"
            )
            self.PIP = os.path.join(
                self._root_dir, "workspace", "venv", "Scripts", "pip.exe"
            )

        def upgrade_pip():
            self._log("Upgrade pip")
            self._os_command(self.PYTHON, "-m", "pip", "install", "--upgrade", "pip")

        def install_packages():
            packages = (
                ("humblewx", None),
                ("icalendar", None),
                ("Markdown", None),
                ("pyinstaller", None),
                ("wxPython", "4.1.1"),
            )
            for package, version in packages:
                self._pip_install(package, version)
            self.PYINSTALLER = os.path.join(
                self._root_dir, "workspace", "venv", "Scripts", "pyinstaller.exe"
            )
            self._log(self._os_command(self.PIP, "list"))

        self._log_header("Create virtual environment")
        create_venv()
        upgrade_pip()
        install_packages()

    def _run_tests(self):
        self._log_header("Run Tests")
        self._os_command(
            self.PYTHON, "tools/execute-specs.py", "--write-testlist", "testlist.txt"
        )

    def _generate_mo_file(self):
        self._log_header("Generate mo files")
        try:
            self._cd(os.path.join(self._root_dir, "workspace", "tools"))
            self._os_command(self.PYTHON, "generate-mo-files.py")
        finally:
            self._cd(os.path.join(self._root_dir, "workspace"))

    def _modify_source_files(self):
        self._log_header("Modify source files")
        self._cd(os.path.join(self._root_dir, "workspace", "tools", "winbuildtools"))
        self._os_command(self.PYTHON, "mod_paths.py", ".")
        self._log("paths.py modified")
        self._log(f"Revision: {self._build_version or self._hg_node}")
        self._os_command(
            self.PYTHON,
            "mod_iss_timeline_version.py",
            ".",
            self._build_version or self._hg_node,
        )
        self._log("version.py modified")

    def _create_timeline_executable(self):
        self._log_header("Create Timeline executable")
        self._cd(os.path.join(self._root_dir, "workspace", "tools", "winbuildtools"))
        self._os_command(self.PYINSTALLER, "timeline.spec")

    def _copy_files_to_dist(self):
        self._log("Copying icons and translations to dist")
        self._cd(os.path.join(self._root_dir, "workspace", "tools", "winbuildtools"))
        self._create_dir(".\\dist\\icons")
        self._create_dir(".\\dist\\icons\\event_icons")
        self._create_dir(".\\dist\\translations")

        self._os_command("xcopy", "/S", "..\\..\\icons\*.*", ".\\dist\icons\\*.*")
        self._log(f"Icons copied")

        for lang in (
            "ca",
            "de",
            "el",
            "es",
            "eu",
            "fr",
            "gl",
            "he",
            "it",
            "ko",
            "lt",
            "nl",
            "pl",
            "pt",
            "pt_BR",
            "ru",
            "sv",
            "tr",
            "vi",
            "zh_CN",
        ):
            self._os_command(
                "xcopy",
                "/S",
                f"..\\..\\translations\\{lang}\\*.*",
                f".\\dist\\translations\\{lang}\\*.*",
            )
            self._log(f"{lang} translations copied")

    def _create_distrubtable(self):
        self._log_header("Create distributable")
        self._copy_files_to_dist()
        self._cd(
            os.path.join(self._root_dir, "workspace", "tools", "winbuildtools", "inno")
        )
        self._os_command("iscc.exe", "timeline2Win32.iss")

    def _move_result(self):
        self._log_header("Move result")
        self._cd(
            os.path.join(
                self._root_dir, "workspace", "tools", "winbuildtools", "inno", "out"
            )
        )
        self._os_command("xcopy", "/Y", "*.exe", f"{self._root_dir}\\*.*")
        self._log("Resulting exe-file moved to root dir.")

    def _log_header(self, text):
        print("-" * 50)
        print(f" {text}")
        print("-" * 50)

    def _log(self, text):
        print(f"  {text}")

    def _abort_message(self, ex):
        self._log_header("Build aborted:")
        self._log(ex)

    def _clean_up(self):
        self._log_header("Clean up")
        self._cd("root")
        if os.path.isdir("workspace"):
            os.system("rmdir /S /Q workspace")
            self._log("workspace directory removed")
        else:
            self._log("No workspace found")
        if os.path.isdir("__pycache__"):
            os.system("rmdir /S /Q __pycache__")
            self._log("__pycache__ directory removed")

    def run(self):
        try:
            self._clean_up()
            self._validate_prerequisite()
            self._create_workspace()
            self._get_timeline_from_repo()
            self._create_virtual_environment()
            self._run_tests()
            self._generate_mo_file()
            self._modify_source_files()
            self._create_timeline_executable()
            self._create_distrubtable()
            self._move_result()
        except BuildError as ex:
            self._abort_message(ex)
        finally:
            if not self._keep_workspace:
                self._clean_up()


def print_help():
    print(
        """
Usage:

    python build_win_setup_executable.py [options]

Options:

    -h, --help                          Print help
    -p dirname, --python-home dirname   Path to dir where python.exe can be found. Default = c:\pgm\python396
    -t tagname, --tag tagname           Tagged version to build. Default = latest code
    -k, --keep-workspace                Kepp the workspace directory when finished

        """
    )


if __name__ == "__main__":
    DEFAULT_PYTHON_HOME = "c:\\pgm\\Python396"
    specs = [
        NamedArg(name="t", long_name="tag", has_value=True),
        NamedArg(name="k", long_name="keep-workspace"),
        NamedArg(name="p", long_name="python-home", has_value=True),
        NamedArg(name="h", long_name="help"),
    ]
    parser = CmdLineParser(specs)
    if parser.help:
        print_help()
    else:
        build_version = parser.tag_value
        python_home = (
            parser.python_home_value
            if parser.python_home_value
            else DEFAULT_PYTHON_HOME
        )
        Build(
            python_home=python_home,
            build_version=build_version,
            keep_workspace=parser.keep_workspace,
        ).run()
