#! /usr/bin/env python
"""Update director image symlinks for post install and pre-uninstall"""

from __future__ import print_function

import argparse
import os
import re
import subprocess

from platform import machine


def _get_args():
    """
    Setup and parse command line arguments

    return: Parsed argument object.
    """
    parser = argparse.ArgumentParser()
    parser.add_argument('--rm', type=str, default=None,
                        help='The OSP version-release being removed (E.G. 13.0-20180803.1)')
    return parser.parse_args()


def _get_newer(first, second):
    """
    Get the newer of first and second

    param: first The first NVR to compare
    param: second The second NVR to compare
    return: The newest out of the two NVRs
    """
    newer = first
    if first != '' and second != '':
        cmd = ['/usr/bin/rpmdev-vercmp', first, second]
        # rpmdev-vercmp only returns 0 if first and second are the same
        # 11 is returned if first is newer
        # 12 is returned if second is newer
        try:
            # Lets hide the output
            with open(os.devnull, 'w') as dev_null:
                subprocess.check_call(cmd, stdout=dev_null, stderr=dev_null)
        except subprocess.CalledProcessError as error:
            if error.returncode == 12:
                newer = second
    elif first == '' and second != '':
        newer = second

    return newer


def _generate_file_data(file_data, root_dir, filelist, version_release):
    """
    Generate categories of files for further processing

    param: file_data The dictionary to update.
    param: root_dir The directory we are processing
    param: filelist A list of files to process
    param: version_release The version + release data E.G. 13.0-20180802.1
    """
    # file name format component_name-XX.0-20YYMMDD.X.el7ost.Z
    # we want the version (XX.0) and release (20YYMMDD.X)
    arch_extract = re.compile(r'.(x86_64|ppc64le).')
    for file_name in filelist:
        full_path = os.path.join(root_dir, file_name)
        if os.path.islink(full_path):
            os.remove(full_path)
        else:
            nvr_extract = file_name.rsplit('-', 2)
            # If we are uninstalling then ignore matching version_release
            if len(nvr_extract) < 3 or \
               (version_release is not None and '-'.join(nvr_extract[1:]) == version_release):
                continue

            component = nvr_extract[0]
            # collect components and place them in the correct data group.
            if component == 'version':
                file_data['versions'].append(full_path)
            else:
                # Let's combine files by their version number so we can later determine
                # the newest one of the group.
                file_osp_version = nvr_extract[1]
                # Lets extract that arch portion of the NVR, default to X86_64 since older
                # releases were not multi-arch.
                arch = 'x86_64'
                arch_match = arch_extract.search(nvr_extract[2])
                if arch_match is not None:
                    arch = arch_match.group(1)

                if component not in file_data['files']:
                    file_data['files'][component] = {}

                if file_osp_version not in file_data['files'][component]:
                    file_data['files'][component][file_osp_version] = {}

                if arch not in file_data['files'][component][file_osp_version]:
                    file_data['files'][component][file_osp_version][arch] = []

                file_data['files'][component][file_osp_version][arch].append(full_path)


def _create_symlink(target, link_name, **kwargs):
    """
    Create a symlink on the filesystem

    param: target The real file path we are linking too
    param: link_name The name of the symlink
    """
    link_root = kwargs.get('link_root', os.path.dirname(target))
    link_path = os.path.join(link_root, link_name)
    os.symlink(target, link_path)


def _update_version_link(file_list, link_root):
    """
    Update the version.txt symlink to the latest installed version

    param: file_list A list of version text files
    """
    max_version_file = ""
    for ver_file in file_list:
        max_version_file = _get_newer(max_version_file, ver_file)

    _create_symlink(max_version_file, 'version.txt', link_root=link_root)


def _update_tar_links(tar_dict, link_root):
    """
    Update all symlinks for tar files

    param: tar_dict A dictionary of filenames, versions and the full paths to the files
    """
    for root_name, data in tar_dict.items():
        overall_max = ""  # the newest file out of all the versions
        for version, arches in data.items():
            for arch, files in arches.items():
                max_file = ""  # the newest file in this version
                for file_name in files:
                    max_file = _get_newer(max_file, file_name)

                extension = os.path.splitext(max_file)[1]
                link_name = '-'.join([root_name, 'latest', str(version), arch]) + extension
                _create_symlink(max_file, link_name, link_root=link_root)
                if arch == machine():
                    ver_link = '-'.join([root_name, 'latest', str(version)]) + extension
                    _create_symlink(max_file, ver_link, link_root=link_root)
                    overall_max = _get_newer(overall_max, max_file)

        if overall_max != "":
            overall_extension = os.path.splitext(overall_max)[1]
            _create_symlink(overall_max,
                            root_name + '-latest' + overall_extension, link_root=link_root)
            _create_symlink(overall_max,
                            root_name + overall_extension, link_root=link_root)


def _main():
    """
    Main execution function
    """
    args = _get_args()
    path = '/usr/share/rhosp-director-images'  # image storage location
    file_data = {'versions': [], 'files': {}}
    for dirname, _, filelist in os.walk(path):
        _generate_file_data(file_data, dirname, filelist, args.rm)

    if file_data['files']:
        _update_tar_links(file_data['files'], path)

    if file_data['versions']:
        _update_version_link(file_data['versions'], path)


if __name__ == '__main__':
    _main()
