# Copyright (c) 2013, 2015-2017 ARM Limited
# All rights reserved.
#
# The license below extends only to copyright in the software and shall
# not be construed as granting a license to any other intellectual
# property including but not limited to intellectual property relating
# to a hardware implementation of the functionality of the software
# licensed hereunder.  You may use the software subject to the license
# terms below provided that you ensure that this notice is replicated
# unmodified and in its entirety in all distributions of the software,
# modified or unmodified, in source code or in binary form.
#
# Copyright (c) 2011 Advanced Micro Devices, Inc.
# Copyright (c) 2009 The Hewlett-Packard Development Company
# Copyright (c) 2004-2005 The Regents of The University of Michigan
# 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 holders 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
# OWNER 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 os
import sys
import tempfile
import textwrap

from gem5_scons.util import get_termcap
from gem5_scons.configure import Configure
from gem5_scons.defaults import EnvDefaults
import SCons.Script

termcap = get_termcap()

def strip_build_path(path, env):
    path = str(path)
    build_base = 'build/'
    variant_base = env['BUILDROOT'] + os.path.sep
    if path.startswith(variant_base):
        path = path[len(variant_base):]
    elif path.startswith(build_base):
        path = path[len(build_base):]
    return path

def TempFileSpawn(scons_env):
    old_pspawn = scons_env['PSPAWN']
    old_spawn = scons_env['SPAWN']

    def wrapper(old, sh, esc, cmd, sh_args, *py_args):
        with tempfile.NamedTemporaryFile() as temp:
            temp.write(' '.join(sh_args).encode())
            temp.flush()
            sh_args = [sh, esc(temp.name)]
            return old(sh, esc, sh, sh_args, *py_args)

    def new_pspawn(sh, esc, cmd, args, sh_env, stdout, stderr):
        return wrapper(old_pspawn, sh, esc, cmd, args, sh_env, stdout, stderr)
    def new_spawn(sh, esc, cmd, args, sh_env):
        return wrapper(old_spawn, sh, esc, cmd, args, sh_env)

    scons_env['PSPAWN'] = new_pspawn
    scons_env['SPAWN'] = new_spawn

# Generate a string of the form:
#   common/path/prefix/src1, src2 -> tgt1, tgt2
# to print while building.
class Transform(object):
    # all specific color settings should be here and nowhere else
    tool_color = termcap.Normal
    pfx_color = termcap.Yellow
    srcs_color = termcap.Yellow + termcap.Bold
    arrow_color = termcap.Blue + termcap.Bold
    tgts_color = termcap.Yellow + termcap.Bold

    def __init__(self, tool, max_sources=99):
        self.format = self.tool_color + (" [%8s] " % tool) \
                      + self.pfx_color + "%s" \
                      + self.srcs_color + "%s" \
                      + self.arrow_color + " -> " \
                      + self.tgts_color + "%s" \
                      + termcap.Normal
        self.max_sources = max_sources

    def __call__(self, target, source, env, for_signature=None):
        # truncate source list according to max_sources param
        source = source[0:self.max_sources]
        def strip(f):
            return strip_build_path(str(f), env)
        if len(source) > 0:
            srcs = list(map(strip, source))
        else:
            srcs = ['']
        tgts = list(map(strip, target))
        # surprisingly, os.path.commonprefix is a dumb char-by-char string
        # operation that has nothing to do with paths.
        com_pfx = os.path.commonprefix(srcs + tgts)
        com_pfx_len = len(com_pfx)
        if com_pfx:
            # do some cleanup and sanity checking on common prefix
            if com_pfx[-1] == ".":
                # prefix matches all but file extension: ok
                # back up one to change 'foo.cc -> o' to 'foo.cc -> .o'
                com_pfx = com_pfx[0:-1]
            elif com_pfx[-1] == "/":
                # common prefix is directory path: OK
                pass
            else:
                src0_len = len(srcs[0])
                tgt0_len = len(tgts[0])
                if src0_len == com_pfx_len:
                    # source is a substring of target, OK
                    pass
                elif tgt0_len == com_pfx_len:
                    # target is a substring of source, need to back up to
                    # avoid empty string on RHS of arrow
                    sep_idx = com_pfx.rfind(".")
                    if sep_idx != -1:
                        com_pfx = com_pfx[0:sep_idx]
                    else:
                        com_pfx = ''
                elif src0_len > com_pfx_len and srcs[0][com_pfx_len] == ".":
                    # still splitting at file extension: ok
                    pass
                else:
                    # probably a fluke; ignore it
                    com_pfx = ''
        # recalculate length in case com_pfx was modified
        com_pfx_len = len(com_pfx)
        def fmt(files):
            f = list(map(lambda s: s[com_pfx_len:], files))
            return ', '.join(f)
        return self.format % (com_pfx, fmt(srcs), fmt(tgts))

# The width warning and error messages should be wrapped at.
text_width = None

# If stdout is not attached to a terminal, default to 80 columns.
if not sys.stdout.isatty():
    text_width = 80

# This should work in python 3.3 and above.
if text_width is None:
    try:
        import shutil
        text_width = shutil.get_terminal_size().columns
    except:
        pass

# This should work if the curses python module is installed.
if text_width is None:
    try:
        import curses
        try:
            _, text_width = curses.initscr().getmaxyx()
        finally:
            curses.endwin()
    except:
        pass

# If all else fails, default to 80 columns.
if text_width is None:
    text_width = 80

def print_message(prefix, color, message, **kwargs):
    prefix_len = len(prefix)
    if text_width > prefix_len:
        wrap_width = text_width - prefix_len
        padding = ' ' * prefix_len

        # First split on newlines.
        lines = message.split('\n')
        # Then wrap each line to the required width.
        wrapped_lines = []
        for line in lines:
            wrapped_lines.extend(textwrap.wrap(line, wrap_width))
        # Finally add the prefix and padding on extra lines, and glue it all
        # back together.
        message = prefix + ('\n' + padding).join(wrapped_lines)
    else:
        # We have very small terminal, indent formatting doesn't help.
        message = prefix + message
    # Add in terminal escape sequences.
    message = color + termcap.Bold + message + termcap.Normal
    # Actually print the message.
    print(message, **kwargs)
    return message

all_warnings = []
def summarize_warnings():
    if not all_warnings:
        return
    print(termcap.Yellow + termcap.Bold +
            '*** Summary of Warnings ***' +
            termcap.Normal)
    list(map(print, all_warnings))

def warning(*args, **kwargs):
    message = ' '.join(args)
    printed = print_message('Warning: ', termcap.Yellow, message, **kwargs)
    all_warnings.append(printed)

def error(*args, **kwargs):
    message = ' '.join(args)
    print_message('Error: ', termcap.Red, message, **kwargs)
    SCons.Script.Exit(1)

def parse_build_path(target):
    path_dirs = target.split('/')

    # Pop off the target file.
    path_dirs.pop()

    # Search backwards for the "build" directory. Whatever was just before it
    # was the name of the variant.
    variant_dir = path_dirs.pop()
    while path_dirs and path_dirs[-1] != 'build':
        variant_dir = path_dirs.pop()
    if not path_dirs:
        error("No non-leaf 'build' dir found on target path.", t)

    return os.path.join('/', *path_dirs), variant_dir

# The MakeAction wrapper, and a SCons tool to set up the *COMSTR variables.
if SCons.Script.GetOption('verbose'):
    def MakeAction(action, string, *args, **kwargs):
        return SCons.Script.Action(action, *args, **kwargs)

    def MakeActionTool(env):
        pass
else:
    MakeAction = SCons.Script.Action

    def MakeActionTool(env):
        env['CCCOMSTR']        = Transform("CC")
        env['CXXCOMSTR']       = Transform("CXX")
        env['ASCOMSTR']        = Transform("AS")
        env['ARCOMSTR']        = Transform("AR", 0)
        env['LINKCOMSTR']      = Transform("LINK", 0)
        env['SHLINKCOMSTR']    = Transform("SHLINK", 0)
        env['RANLIBCOMSTR']    = Transform("RANLIB", 0)
        env['M4COMSTR']        = Transform("M4")
        env['SHCCCOMSTR']      = Transform("SHCC")
        env['SHCXXCOMSTR']     = Transform("SHCXX")

__all__ = ['Configure', 'EnvDefaults', 'Transform', 'warning', 'error',
           'MakeAction', 'MakeActionTool']
