#!/usr/bin/python3

# Copyright (C) 2019-2020  Patryk Obara <patryk.obara@gmail.com>
# SPDX-License-Identifier: GPL-2.0-or-later
#
# 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 2 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

"""
Install DOS games from gog.com into the Steam library.

You can drop this script anywhere in your PATH (it has no dependencies besides
python3 standard library and Wine).
"""

# pylint: disable=invalid-name
# pylint: disable=missing-docstring

import argparse
import glob
import json
import os
import subprocess
import sys
import tempfile
import shutil


DATA_HOME = os.environ.get('XDG_DATA_HOME') or \
            os.path.expanduser('~/.local/share')

# TODO Check for Wine 3.16 or newer; older Wine will leave desktop entries.


def run_setup_file(setup_exe, pfx):
    tmp_env = os.environ
    tmp_env.update({
        'WINEDLLOVERRIDES': 'winemenubuilder.exe=d',
        'WINEPREFIX': pfx
    })
    inno_flags = ['/NOGUI', '/SUPPRESSMSGBOXES', '/SILENT', '/DIR=C:\\game']
    cmd = ['wine', setup_exe] + inno_flags
    process = subprocess.run(cmd,
                             check=False,
                             env=tmp_env,
                             stderr=subprocess.DEVNULL)
    if process.returncode != 0:
        print('{} finished with code {}'.format(setup_exe, process.returncode))
        sys.exit(10 + process.returncode)


def find_gog_info(game_dir):
    info_files = glob.glob(game_dir + '/goggame-*.info')
    if not info_files:
        print('goggame-*.info file not found')
        sys.exit(3)
    with open(info_files[0], 'r') as gog_info_file:
        info = json.load(gog_info_file)
        return info
    return None


# Trying to emulate GOG name -> slug conversion as close as possible:
#
def name_to_slug(name):
    name = name.strip()
    if name.endswith(', The'):
        name = 'The ' + name[:-5]
    name = name + '_'  # to make it easier to transform Roman numerals
    name = name.replace('&', 'and')
    name = name.translate(str.maketrans(' ', '_', r""":™®,."-'!?"""))
    name = name.replace('_IX_', '_9_')
    name = name.replace('_VIII_', '_8_')
    name = name.replace('_VII_', '_7_')
    name = name.replace('_VI_', '_6_')
    name = name.replace('_V_', '_5_')
    name = name.replace('_IV_', '_4_')
    name = name.replace('_III_', '_3_')
    name = name.replace('_II_', '_2_')
    name = name.replace('__', '_')
    name = name.strip('_')
    return name.lower()


def available_dir(dirname):
    if not os.path.exists(dirname):
        return True
    if os.path.isdir(dirname) and any(True for _ in os.scandir(dirname)):
        return False
    return True


def remove_prefix(pfx, word):
    if word.startswith(pfx):
        return word[len(pfx):]
    return word


def create_launcher_files(path, gog_info):
    name = gog_info['name']
    slug = name_to_slug(name)
    task = gog_info['playTasks'][0]
    args = task.get('arguments') or ''
    wdir = task.get('workingDir') or ''
    launcher_bat = os.path.join(path, wdir, '{}.bat'.format(slug))
    gog_path = task.get('path')
    exe = remove_prefix(wdir + '\\', gog_path) if wdir else gog_path

    with open(launcher_bat, 'w') as batch:
        batch.write('{} {}\n'.format(exe, args))

    os.chmod(launcher_bat, 0o775)

    with open('{path}/{slug}.desktop'.format(**locals()), 'w') as entry:
        entry.write('[Desktop Entry]\n')
        entry.write('Name={name}\n'.format(**locals()))
        entry.write('Type=Application\n')
        entry.write('Exec={launcher_bat}\n'.format(**locals()))
        entry.write('Encoding=UTF-8\n')
        entry.write('StartupNotify=true\n')


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('setup_file', help='.exe installer for GOG game')
    args = parser.parse_args()

    setup_exe = args.setup_file

    if not os.path.isfile(setup_exe):
        print('File {} does not exist'.format(setup_exe), file=sys.stderr)
        sys.exit(1)

    if not setup_exe.lower().endswith('.exe'):
        print('Only .exe installers supported', file=sys.stderr)
        sys.exit(1)

    with tempfile.TemporaryDirectory() as tmp_dir:
        print('Unpacking game files to temporary directory: ' + tmp_dir,
              file=sys.stderr)
        run_setup_file(setup_exe, tmp_dir)
        game_dir = os.path.join(tmp_dir, 'drive_c/game')
        gog_info = find_gog_info(game_dir)
        game_name = gog_info['name']
        game_slug = name_to_slug(game_name)
        install_path = os.path.join(DATA_HOME, 'games', game_slug)

        if not available_dir(install_path):
            print('Path {} is occupied already'.format(install_path))
            sys.exit(4)

        # ensure all directories leading to install_path exist
        os.makedirs(install_path, exist_ok=True)
        os.rmdir(install_path)

        print('Movig game files to {}'.format(install_path))
        shutil.move(game_dir, install_path)
        os.chmod(install_path, 0o775)

        create_launcher_files(install_path, gog_info)

        print('\nInstallation finished.\n')
        print('Select the following file when adding non-Steam game',
              'to your library:')
        print('{}/{}.desktop'.format(install_path, game_slug))


if __name__ == "__main__":
    main()
