# Copyright (C) 2017 Guillaume Valadon <guillaume@valadon.net>

"""
Functions used by several flashre modules
"""


import os
import cPickle as pickle

import r2pipe


def cache(filename):
    """
    A simple decorator to cache results to disk.
    """

    def decorator(func):
        """Note: it is the function that is finally returned"""
        def cached_function(*args):
            """Note: needed to access the returned value"""
            try:
                return pickle.load(open(filename, "r"))
            except IOError:
                value = func(*args)
                pickle.dump(value, open(filename, "w"))
                return value
        return cached_function

    return decorator


def get_r2pipe(filename, offset, options=None):
    """
    Get a r2pipe handle ready to analyse a flashair binary.
    """

    # Set the miasm architecture
    os.putenv("R2M2_ARCH", "mepl")

    # Use the r2m2 architecture
    default_options = ["-a", "r2m2"]

    # Map the binary at a given location
    default_options += ["-m", hex(offset)]

    # Decrease r2 verbosity
    default_options += ["-e", "bin.verbose=false"]

    # Add user specified options
    if isinstance(options, list):
        default_options += options

    return r2pipe.open(filename, default_options)


@cache("get_strings.cache")
def get_strings(r2p):
    """
    Return all strings in the binary and their offsets.
    """

    ret = r2p.cmdj("izzj")
    return [(s["paddr"], s["string"].decode("base64")) for s in ret]


def r2_search_memory(r2p, pattern):
    """
    Search for a pattern in the binary and return matched offsets.
    """

    return [p["offset"] for p in r2p.cmdj("/xj %s" % pattern)]


@cache("get_prologues.cache")
def get_prologues(r2p):
    """
    Look for well-known MeP prologues in a binary using r2pipe.
    """

    # Type #1 example:
    #   c06f           ADD SP, -16
    #   1a70           LDC R0, LP
    ret = [p["offset"] for p in r2p.cmdj("/xj ..6f1a70")]

    # Type #2 example:
    #   f0cfdcff       ADD3 SP, SP, -36
    #   1a70           LDC R0, LP
    ret += [p["offset"] for p in r2p.cmdj("/xj f0cf....1a70")]

    # Type #3 example:
    #   1a70           LDC R0, LP
    #   f06f           ADD SP, -4
    ret += [p["offset"] for p in r2p.cmdj("/xj 1a70.06f")]

    # Type #4 example:
    #   1a70           LDC R0, LP
    #   f0cfd8ff       ADD3 SP, SP, -40
    ret += [p["offset"] for p in r2p.cmdj("/xj 1a70f0cf")]

    return ret


@cache("get_calls.cache")
def get_calls(r2p):
    """
    Look for BSR (aka calls) in a binary using r2pipe.
    """

    # Major Opcode #11 - 2 bytes BSR example:
    # Example:
    #   51b8           BSR 0x5C21
    bsr2_pattern = "01b0:0ef0"  # mask needed due to the instruction encoding
    ret = r2_search_memory(r2p, bsr2_pattern)

    # Major Opcode #13 - 4 bytes BSR example:
    # Example:
    #   99dc5506       BSR 0x78178
    bsr4_pattern = "09d80500:0fd80f00"  # mask needed due to the instruction encoding
    ret += r2_search_memory(r2p, bsr4_pattern)

    return ret


def is_camelcase_str(string):
    """Return True if string uses CamelCase."""

    # Discard non alpha strings
    if ' ' in string and not string.isalpha():
        return False

    # Look for two uppercase characters
    uppers = [c for c in zip(string, xrange(len(string))) if c[0].isupper()]
    if len(uppers) < 2:
        return False

    # Check if they are followed by lowercase ones
    try:
        for up_char in uppers:
            after_char = string[up_char[1]+1]
            if not after_char.islower():
                return False
    except IndexError:
        return False

    return True


def args_detect_int(i):
    """Simple trick to specify hex integer to argparse."""
    return int(i, 0)