# Much of this file borrowed from conda/core/portability.py:
#
# https://github.com/conda/conda/blob/master/conda/core/portability.py
#
# The license of which has been provided below:
#
# -----------------------------------------------------------------------------
#
# BSD 3-Clause License
#
# Copyright (c) 2012, Continuum Analytics, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of the copyright holder nor the names of its
#       contributors may be used to endorse or promote products
#       derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

import platform
import re
import struct
import subprocess
import sys

on_win = sys.platform == 'win32'

# three capture groups: whole_shebang, executable, options
SHEBANG_REGEX = (
        # pretty much the whole match string
        br'^(#!'
        # allow spaces between #! and beginning of the executable path
        br'(?:[ ]*)'
        # the executable is the next text block without an escaped
        # space or non-space whitespace character
        br'(/(?:\\ |[^ \n\r\t])*)'
        # the rest of the line can contain option flags
        br'(.*)'
        # end whole_shebang group
        br')$')


def update_prefix(path, new_prefix, placeholder, mode='text'):
    if on_win and mode == 'text':
        # force all prefix replacements to forward slashes to simplify need to
        # escape backslashes replace with unix-style path separators
        new_prefix = new_prefix.replace('\\', '/')

    file_changed = False
    with open(path, 'rb+') as fh:
        original_data = fh.read()
        fh.seek(0)

        data = replace_prefix(original_data, mode, placeholder, new_prefix)

        # If the before and after content is the same, skip writing
        if data != original_data:
            fh.write(data)
            fh.truncate()
            file_changed = True

    if file_changed and platform.system() == "Darwin" and platform.machine() == "arm64":
        subprocess.run(
            ["/usr/bin/codesign", "-s", "-", "-f", path], capture_output=True
        )


def replace_prefix(data, mode, placeholder, new_prefix):
    if mode == 'text':
        data2 = text_replace(data, placeholder, new_prefix)
    elif mode == 'binary':
        data2 = binary_replace(data,
                               placeholder.encode('utf-8'),
                               new_prefix.encode('utf-8'))
        if not on_win and len(data2) != len(data):
            message = ("Found mismatched data length in binary file:\n"
                       "original data length: {len_orig!d})\n"
                       "new data length: {len_new!d}\n"
                       ).format(len_orig=len(data),
                                len_new=len(data2))
            raise ValueError(message)
    else:
        raise ValueError("Invalid mode: %r" % mode)
    return data2


def text_replace(data, placeholder, new_prefix):
    return data.replace(placeholder.encode('utf-8'), new_prefix.encode('utf-8'))


if on_win:
    def binary_replace(data, placeholder, new_prefix):
        new_prefix = new_prefix.lower()
        if placeholder in data:
            return replace_pyzzer_entry_point_shebang(data, placeholder, new_prefix)
        elif placeholder.lower() in data:
            return replace_pyzzer_entry_point_shebang(data, placeholder.lower(), new_prefix)
        return data

else:
    def binary_replace(data, placeholder, new_prefix):
        """Perform a binary replacement of `data`, where ``placeholder`` is
        replaced with ``new_prefix`` and the remaining string is padded with null
        characters.  All input arguments are expected to be bytes objects."""

        def replace(match):
            occurances = match.group().count(placeholder)
            padding = (len(placeholder) - len(new_prefix)) * occurances
            if padding < 0:
                raise ValueError("negative padding")
            return match.group().replace(placeholder, new_prefix) + b'\0' * padding

        pat = re.compile(re.escape(placeholder) + b'([^\0]*?)\0')
        return pat.sub(replace, data)


def replace_pyzzer_entry_point_shebang(all_data, placeholder, new_prefix):
    """Code adapted from pyzzer. This is meant to deal with entry point exe's
    created by distlib, which consist of a launcher, then a shebang, then a zip
    archive of the entry point code to run.  We need to change the shebang.
    """
    # Copyright (c) 2013 Vinay Sajip.
    #
    # Permission is hereby granted, free of charge, to any person obtaining a copy
    # of this software and associated documentation files (the "Software"), to deal
    # in the Software without restriction, including without limitation the rights
    # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    # copies of the Software, and to permit persons to whom the Software is
    # furnished to do so, subject to the following conditions:
    #
    # The above copyright notice and this permission notice shall be included in
    # all copies or substantial portions of the Software.
    #
    # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    # THE SOFTWARE.
    launcher = shebang = None
    pos = all_data.rfind(b'PK\x05\x06')
    if pos >= 0:
        end_cdr = all_data[pos + 12:pos + 20]
        cdr_size, cdr_offset = struct.unpack('<LL', end_cdr)
        arc_pos = pos - cdr_size - cdr_offset
        data = all_data[arc_pos:]
        if arc_pos > 0:
            pos = all_data.rfind(b'#!', 0, arc_pos)
            if pos >= 0:
                shebang = all_data[pos:arc_pos]
                if pos > 0:
                    launcher = all_data[:pos]

        if data and shebang and launcher:
            if hasattr(placeholder, 'encode'):
                placeholder = placeholder.encode('utf-8')
            if hasattr(new_prefix, 'encode'):
                new_prefix = new_prefix.encode('utf-8')
            shebang = shebang.replace(placeholder, new_prefix)
            all_data = b"".join([launcher, shebang, data])
    return all_data
