#!/usr/bin/env python3
#
# 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 os
import json
import shutil
import signal
import subprocess
import sys
import tempfile

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


class Error(Exception):
    pass


class Executable(object):

    def __init__(self, exe, *before_args, **kwargs):
        self.exe = exe
        self.before_args = list(before_args)
        self.after_args = []
        self.basename = kwargs.get('basename',
                                   os.path.basename(exe)).replace('.exe', '')
        self.error_cmdline = kwargs.get('error_cmdline', True)
        self.clean_stdout = kwargs.get('clean_stdout')
        self.clean_stderr = kwargs.get('clean_stderr')
        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] + self.before_args + list(args) + self.after_args
        cmd_str = ' '.join(cmd)
        if self.verbose:
            print(cmd_str)

        if self.error_cmdline:
            err_cmd_str = cmd_str.replace('.exe', '')
        else:
            err_cmd_str = self.basename

        stdout = ''
        stderr = ''
        error = None
        try:
            process = subprocess.Popen(cmd, stdout=self.stdout_handle,
                                       stderr=self.stderr_handle, **kwargs)
            stdout, stderr = process.communicate()
            if stdout:
                stdout = stdout.decode('utf-8', 'ignore')
            if stderr:
                stderr = stderr.decode('utf-8', 'ignore')
            if self.clean_stdout:
                stdout = self.clean_stdout(stdout)
            if self.clean_stderr:
                stderr = self.clean_stderr(stderr)
            if process.returncode < 0:
                # Terminated by signal
                signame = SIGNAMES.get(-process.returncode, '<unknown>')
                error = Error('Signal raised running "%s": %s\n%s' % (err_cmd_str,
                              signame, stderr))
            elif process.returncode > 0:
                error = Error('Error running "%s":\n%s' % (err_cmd_str, stderr))
        except OSError as e:
            error = Error('Error running "%s": %s' % (err_cmd_str, str(e)))
        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('%s=%s' % (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 = '%07x: ' % p
        while p < line_end:
            for i in range(DUMP_OCTETS_PER_GROUP):
                if p < end:
                    line += '%02x' % data[p]
                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 += '%c' % x
            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]
