#
# Copyright 2016 WebAssembly Community Group participants
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import contextlib
import json
import os
import shlex
import shutil
import signal
import subprocess
import sys
import tempfile

# Get signal names from numbers in Python
# http://stackoverflow.com/a/2549950
SIGNAMES = {
    k: v for v, k in sorted(signal.__dict__.items(), reverse=True)
    if v.startswith('SIG') and not v.startswith('SIG_')
}


class Error(Exception):
    pass


class Executable:

    def __init__(self, exe, *after_args, **kwargs):
        self.exe = exe
        self.after_args = list(after_args)
        self.basename = kwargs.get('basename',
                                   os.path.basename(exe)).replace('.exe', '')
        self.error_cmdline = kwargs.get('error_cmdline', True)
        self.stdout_handle = self._ForwardHandle(kwargs.get('forward_stdout'))
        self.stderr_handle = self._ForwardHandle(kwargs.get('forward_stderr'))
        self.verbose = False

    def _ForwardHandle(self, forward):
        return None if forward else subprocess.PIPE

    def _RunWithArgsInternal(self, *args, **kwargs):
        cmd = [self.exe] + list(args) + self.after_args
        cmd_str = shlex.join(cmd)
        if self.verbose:
            print(cmd_str)

        if self.error_cmdline:
            err_cmd_str = cmd_str
        else:
            err_cmd_str = self.basename

        stdout = ''
        stderr = ''
        error = None
        try:
            process = subprocess.run(cmd, check=False, text=True,
                                     stdout=self.stdout_handle,
                                     stderr=self.stderr_handle, **kwargs)
            stdout = process.stdout
            stderr = process.stderr
            if process.returncode < 0:
                # Terminated by signal
                signame = SIGNAMES.get(-process.returncode, '<unknown>')
                error = Error(f'Signal raised running "{err_cmd_str}": {signame}\n{stderr}')
            elif process.returncode > 0:
                error = Error(f'Error running "{err_cmd_str}" ({process.returncode}):\n{stdout}\n{stderr}')
        except OSError as e:
            error = Error(f'Error running "{err_cmd_str}": {e!s}')
        return stdout, stderr, error

    def RunWithArgsForStdout(self, *args, **kwargs):
        stdout, _stderr, error = self._RunWithArgsInternal(*args, **kwargs)
        if error:
            raise error
        return stdout

    def RunWithArgs(self, *args, **kwargs):
        stdout, _stderr, error = self._RunWithArgsInternal(*args, **kwargs)
        if stdout:
            sys.stdout.write(stdout)
        if error:
            raise error

    def AppendArg(self, arg):
        self.after_args.append(arg)

    def AppendOptionalArgs(self, option_dict):
        for option, value in option_dict.items():
            if value:
                if value is True:
                    self.AppendArg(option)
                else:
                    self.AppendArg(f'{option}={value}')


@contextlib.contextmanager
def TempDirectory(out_dir, prefix=None):
    if out_dir:
        out_dir_is_temp = False
        if not os.path.exists(out_dir):
            os.makedirs(out_dir)
    else:
        out_dir = tempfile.mkdtemp(prefix=prefix)
        out_dir_is_temp = True

    try:
        yield out_dir
    finally:
        if out_dir_is_temp:
            shutil.rmtree(out_dir)


def ChangeExt(path, new_ext):
    return os.path.splitext(path)[0] + new_ext


def ChangeDir(path, new_dir):
    return os.path.join(new_dir, os.path.basename(path))


def Hexdump(data):
    DUMP_OCTETS_PER_LINE = 16
    DUMP_OCTETS_PER_GROUP = 2

    p = 0
    end = len(data)
    lines = []
    while p < end:
        line_start = p
        line_end = p + DUMP_OCTETS_PER_LINE
        line = f'{p:07x}: '
        while p < line_end:
            for i in range(DUMP_OCTETS_PER_GROUP):
                if p < end:
                    line += f'{data[p]:02x}'
                else:
                    line += '  '
                p += 1
            line += ' '
        line += ' '
        p = line_start
        for i in range(DUMP_OCTETS_PER_LINE):
            if p >= end:
                break
            x = data[p]
            if x >= 32 and x < 0x7f:
                line += f'{x:c}'
            else:
                line += '.'
            p += 1
        line += '\n'
        lines.append(line)

    return lines


def GetModuleFilenamesFromSpecJSON(json_filename):
    with open(json_filename) as json_file:
        json_data = json.load(json_file)
    return [m['filename'] for m in json_data['commands'] if 'filename' in m]
