import os

import process_command_files as pcf


class ProcessWholeArchiveOption():
    def __init__(self, arch, peers=None, libs=None):
        self.arch = arch.upper()
        self.peers = { x : 0 for x in peers } if peers else None
        self.libs = { x : 0 for x in libs } if libs else None
        self.start_wa_marker = '--start-wa'
        self.end_wa_marker = '--end-wa'

    def _match_peer_lib(self, arg, ext):
        key = None
        if arg.endswith(ext):
            key = os.path.dirname(arg)
        return key if key and self.peers and key in self.peers else None

    def _match_lib(self, arg):
        return arg if self.libs and arg in self.libs else None

    def _process_arg(self, arg, ext='.a'):
        peer_key = self._match_peer_lib(arg, ext)
        lib_key = self._match_lib(arg)
        if peer_key:
            self.peers[peer_key] += 1
        if lib_key:
            self.libs[lib_key] += 1
        return peer_key if peer_key else lib_key

    def _check_peers(self):
        if self.peers:
            for key, value in self.peers.items():
                assert value > 0, '"{}" specified in WHOLE_ARCHIVE() macro is not used on link command'.format(key)

    def _construct_cmd_apple(self, args):
        force_load_flag = '-Wl,-force_load,'
        is_inside_wa_markers = False

        cmd = []
        for arg in args:
            if arg.startswith(force_load_flag):
                cmd.append(arg)
            elif arg == self.start_wa_marker:
                is_inside_wa_markers = True
            elif arg == self.end_wa_marker:
                is_inside_wa_markers = False
            elif is_inside_wa_markers:
                cmd.append(force_load_flag + arg)
            else:
                key = self._process_arg(arg)
                cmd.append(force_load_flag + arg if key else arg)

        self._check_peers()

        return cmd

    def _construct_cmd_win(self, args):
        whole_archive_prefix = '/WHOLEARCHIVE:'
        is_inside_wa_markers = False

        def add_prefix(arg, need_check_peers_and_libs):
            key = self._process_arg(arg, '.lib') if need_check_peers_and_libs else arg
            return whole_archive_prefix + arg if key else arg

        def add_whole_archive_prefix(arg, need_check_peers_and_libs):
            if not pcf.is_cmdfile_arg(arg):
                return add_prefix(arg, need_check_peers_and_libs)

            cmd_file_path = pcf.cmdfile_path(arg)
            cf_args = pcf.read_from_command_file(cmd_file_path)
            with open(cmd_file_path, 'w') as afile:
                for cf_arg in cf_args:
                    afile.write(add_prefix(cf_arg, need_check_peers_and_libs) + "\n")
            return arg

        cmd = []
        for arg in args:
            if arg == self.start_wa_marker:
                is_inside_wa_markers = True
            elif arg == self.end_wa_marker:
                is_inside_wa_markers = False
            elif is_inside_wa_markers:
                cmd.append(add_whole_archive_prefix(arg, False))
                continue
            elif self.peers or self.libs:
                cmd.append(add_whole_archive_prefix(arg, True))
            else:
                cmd.append(arg)

        self._check_peers()

        return cmd

    def _construct_cmd_linux(self, args):
        whole_archive_flag = '-Wl,--whole-archive'
        no_whole_archive_flag = '-Wl,--no-whole-archive'

        def replace_markers(arg):
            if arg == self.start_wa_marker:
                return whole_archive_flag
            if arg == self.end_wa_marker:
                return no_whole_archive_flag
            return arg

        args = [replace_markers(arg) for arg in args]

        cmd = []
        is_inside_whole_archive = False
        is_whole_archive = False
        # We are trying not to create excessive sequences of consecutive flags
        # -Wl,--no-whole-archive  -Wl,--whole-archive ('externally' specified
        # flags -Wl,--[no-]whole-archive are not taken for consideration in this
        # optimization intentionally)
        for arg in args:
            if arg == whole_archive_flag:
                is_inside_whole_archive = True
                is_whole_archive = False
            elif arg == no_whole_archive_flag:
                is_inside_whole_archive = False
                is_whole_archive = False
            else:
                key = self._process_arg(arg)
                if not is_inside_whole_archive:
                    if key:
                        if not is_whole_archive:
                            cmd.append(whole_archive_flag)
                            is_whole_archive = True
                    elif is_whole_archive:
                        cmd.append(no_whole_archive_flag)
                        is_whole_archive = False

            cmd.append(arg)

        if is_whole_archive:
            cmd.append(no_whole_archive_flag)

        # There can be an empty sequence of archive files between
        # -Wl, --whole-archive and -Wl, --no-whole-archive flags.
        # As a result an unknown option error may occur, therefore to
        # prevent this case we need to remove both flags from cmd.
        # These flags affects only on subsequent archive files.
        if len(cmd) == 2:
            return []

        self._check_peers()

        return cmd

    def construct_cmd(self, args):
        if self.arch in ('DARWIN', 'IOS', 'IOSSIM'):
            return self._construct_cmd_apple(args)

        if self.arch == 'WINDOWS':
            return self._construct_cmd_win(args)

        return self._construct_cmd_linux(args)


def get_whole_archive_peers_and_libs(args):
    remaining_args = []
    peers = []
    libs = []
    peers_flag = '--whole-archive-peers'
    libs_flag = '--whole-archive-libs'

    next_is_peer = False
    next_is_lib = False
    for arg in args:
        if arg == peers_flag:
            next_is_peer = True
        elif arg == libs_flag:
            next_is_lib = True
        elif next_is_peer:
            peers.append(arg)
            next_is_peer = False
        elif next_is_lib:
            libs.append(arg)
            next_is_lib = False
        else:
            remaining_args.append(arg)
    return remaining_args, peers, libs