#!/usr/bin/python
import os
import sys
import string
import subprocess

import conf
make_vmlinux_cmd = conf.make_vmlinux_cmd

def edit_file(filename, lineno_to_edit):
    backup_name = filename + ".backup"
    os.rename(filename, backup_name)
    try:
        file = open(backup_name, "r")
        newfile = open(filename, "w")
        sys.stderr.write("Creating {0}\n".format(backup_name))
        lineno = 0
        while 1:
            line = file.readline()
            if not line:
                break
            lineno += 1
            #sys.stderr.write("{0}: lineno:{1} to_edit:{2}\n".format(filename, lineno, lineno_to_edit))
            if lineno != lineno_to_edit:
                newfile.write(line)
                continue
            pos = line.find("__always_inline")
            size = len("__always_inline")
            if pos < 0:
                pos = line.find("__inline__")
                size = len("__inline__")
            if pos < 0:
                pos = line.find("__inline")
                size = len("__inline")
            if pos < 0:
                pos = line.find("inline")
                size = len("inline")
            if pos < 0:
                sys.stderr.write("{0}:{1}: can't find 'inline'\n".format(filename, lineno))
                os.rename(backup_name, filename)
                backup_name = None
                break
            #sys.stderr.write("{0}: line to patch:'{1}' pos:{2} {3} {4}\n".format(filename, line.rstrip("\n"), pos, line[pos], line[pos + size]))
            if pos + size < len(line) and not line[pos + size].isspace():
                sys.stderr.write("{0}:{1}: can't find 'inline'\n".format(filename, lineno))
                os.rename(backup_name, filename)
                backup_name = None
                break
            if pos > 0 and not line[pos-1].isspace():
                sys.stderr.write("{0}:{1}: can't find 'inline'\n".format(filename, lineno))
                os.rename(backup_name, filename)
                backup_name = None
                break
            if pos > 0:
                line = line[0:pos-1] + " noinline __attribute__((noclone))" + line[pos + size:]
                # note: ^^^^^^^^^^^^-this removes one preceding whitespace char too!
            else:
                line = line[pos + size:]
                if line[0] == ' ':
                    line = line[1:]
                line = "noinline __attribute__ ((noclone))" + line
            newfile.write(line)
            sys.stderr.write("line replaced: {0}".format(line))
        file.close()
    except KeyboardInterrupt:
        os.rename(backup_name, filename)
        raise
    #sys.exit(0)
    return backup_name

def execute(args):
    # Redirect input too and send EOF immediately, just in case process tries to get some input
    #sys.stderr.write("args:{0}\n".format(str(args)))
    proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    stdout, stderr = proc.communicate(input="")
    return stdout, proc.returncode

def execute_or_die(args):
    lines, exitcode = execute(args)
    if exitcode != 0:
        sys.stderr.write("error running {0}\n".format(string.join(args)))
        sys.stderr.write(lines)
        sys.exit(exitcode)
    return lines

def size_objfile(objname):
    # Using "size" wuld miss sections like "__ex_table"
    lines = execute_or_die(["size", "-A", "--", objname])
    lines = lines.splitlines()
    sum = 0
    for line in lines:
        if not line:
            continue
        if line.startswith(".comment"):
            continue
        if line.startswith(".debug_"):
            continue
        if line.startswith("Total"):
            #sys.stdout.write("line:" + line + "\n")
            continue
        try:
            sz = int(line.split()[1])
        except ValueError:
            continue
        sum += sz
    #sys.stdout.write("total:" + str(sum) + "\n")
    return sum

def make_vmlinux():
    try:
        devnull = open("/dev/null", "w")
        sys.stderr.write("Rebuilding vmlinux...\n")
        lines, exitcode = execute(["sh", "-c", make_vmlinux_cmd])
        if exitcode != 0:
            sys.stderr.write("error rebuilding vmlinux\n")
            sys.stderr.write(lines)
            return 0, 0
        vmlinux_size = size_objfile("vmlinux")
        # "exit 0" because grep will exit 1 if no strings found
        lines = execute_or_die(["sh", "-c", "nm --size-sort vmlinux | grep ' {0}$'; exit 0".format(funcname)])
        lines = lines.splitlines()
        size = 0
        sum_size = 0
        sys.stderr.write("deinlined sizes:")
        for line in lines:
            if line:
                size = int(line.split(" ",1)[0], 16)
                sys.stderr.write(" {0}".format(size))
                sum_size += size
                #sys.stderr.write("size:{0}\n".format(size))
        # if there's more than one deinlined copy, "new" bzImage
        # is extra big. Correcting this by subtracting sum_size -
        # the sum of all deinlined copies sans one.
        sum_size -= size
        sys.stderr.write(" total:{0}\n".format(sum_size))
        sys.stderr.write("     vmlinux size:{0}\n".format(vmlinux_size))
        vmlinux_size -= sum_size
        sys.stderr.write("new  vmlinux size:{0}\n".format(vmlinux_size))
        sys.stderr.write("orig_vmlinux size:{0}\n".format(orig_vmlinux_size))
        saved = orig_vmlinux_size - vmlinux_size
        sys.stderr.write("saved:{0}\n".format(saved))
        return saved, size
    except KeyboardInterrupt:
        raise
    return 0, 0

#try:
#    os.mkdir("patches")
#except OSError:
#    pass

#lines, exitcode = execute(["git", "diff"])
#if exitcode != 0 or lines:
#    sys.stderr.write("git diff isn't showing a clean tree\n")
#    sys.exit(1)

orig_vmlinux_size = size_objfile("vmlinux.original")

for line in sys.stdin:
    line = line.rstrip("\n")
    filename, lineno, funcname, inline_len = line.split(":", 3)
    lineno = int(lineno)
    if not filename.endswith(".h"):
        continue
    sys.stderr.write("testing: {0}\n".format(line))
    backup_name = edit_file(filename, lineno)
    if backup_name:
        try:
            saved_bytes, inline_size = make_vmlinux()
            sys.stdout.write("{0}:{1}:{2}:{3}:{4}:{5}\n".format(filename, lineno, funcname, inline_len, saved_bytes, inline_size))
            sys.stdout.flush()
            #if saved_bytes > 0:
            #    #lines, exitcode = execute(["diff", "-u", "--", backup_name, filename])
    	    #    #sys.stdout.write(lines)
            #    #sys.stdout.flush()
            #    lines, exitcode = execute(["git", "commit", "-as",
            #            "-m", "{0}: deinline {1}, save {2} bytes".format(filename, funcname, saved_bytes)
            #    ])
            #    if exitcode != 0:
            #        sys.stderr.write("{0}: git commit failure\n".format(filename))
            #        sys.stderr.write(lines)
            #        break
            #    # TODO: add To: headers
            #    # To: devicetree@vger.kernel.org
            #    # To: linux-kernel@vger.kernel.org
            #    # To: linux-wireless@vger.kernel.org
            #    # To: linux-omap@vger.kernel.org
            #    # To: based on "git log FILE.c | grep ^Author | sort | uniq -c | sort -rn"
            #    lines, exitcode = execute(["git", "format-patch", "-o", "patches", "HEAD~1"])
            #    if exitcode != 0:
            #        sys.stderr.write("{0}: git format-patch failure\n".format(filename))
            #        sys.stderr.write(lines)
            #        break
            #    lines, exitcode = execute(["git", "reset", "--hard", "HEAD~1"])
            #    if exitcode != 0:
            #        sys.stderr.write("{0}: git reset failure\n".format(filename))
            #        sys.stderr.write(lines)
            #        break
        except KeyboardInterrupt:
            raise
        finally:
            sys.stderr.write("Restoring {0}\n".format(filename))
            os.rename(backup_name, filename)
            # touch file
            os.utime(filename, None)
