#!/usr/bin/env python

import argparse
import re
import os
import subprocess
import sys


class ESP32CrashParser(object):
    def __init__(self, toolchain_path, elf_path):
        self.toolchain_path = toolchain_path
        self.gdb_path = os.path.join(toolchain_path, "bin", "xtensa-esp32-elf-gdb")
        self.addr2line_path = os.path.join(toolchain_path, "bin", "xtensa-esp32-elf-addr2line")

        if not os.path.exists(self.gdb_path):
            raise Exception("GDB for ESP not found in {} - {} does not exist.\nUse --toolchain to point to "
                            "your toolchain folder.".format(self.toolchain_path, self.gdb_path))

        if not os.path.exists(self.addr2line_path):
            raise Exception("addr2line for ESP not found in {} - {} does not exist.\nUse --toolchain to point to "
                            "your toolchain folder.".format(self.toolchain_path, self.addr2line_path))

        self.elf_path = elf_path
        if not os.path.exists(self.elf_path):
            raise Exception("ELF file not found: '{}'".format(self.elf_path))

    def parse_text(self, text):
        m = re.search('Backtrace: (.*)', text)
        if m:
            print "Stack trace:"
            for l in self.parse_stack(m.group(1)):
                print "  " + l
        else:
            print "No stack trace found."

    '''
    Decode one stack or backtrace.
    
    See: https://github.com/me-no-dev/EspExceptionDecoder/blob/master/src/EspExceptionDecoder.java#L402
    '''
    def parse_stack(self, text):
        r = re.compile('40[0-2][0-9a-fA-F]{5}')
        m = r.findall(text)
        return self.decode_function_addresses(m)

    def decode_function_address(self, address):
        args = [self.addr2line_path, "-e", self.elf_path, "-aipfC", address]
        return subprocess.check_output(args).strip()

    def decode_function_addresses(self, addresses):
        out = []
        for a in addresses:
            out.append(self.decode_function_address(a))
        return out

    '''
    GDB Should produce line number: https://github.com/me-no-dev/EspExceptionDecoder/commit/a78672da204151cc93979a96ed9f89139a73893f
    However it does not produce anything for me. So not using it for now.
    '''
    def decode_function_addresses_with_gdb(self, addresses):
        args = [self.gdb_path, "--batch"]

        # Disable user config file which might interfere here
        args.extend(["-iex", "set auto-load local-gdbinit off"])

        args.append(self.elf_path)

        args.extend(["-ex", "set listsize 1"])
        for address in addresses:
            args.append("-ex")
            args.append("l *0x{}".format(address))
        args.extend(["-ex", "q"])

        print "Running: {}".format(args)
        out = subprocess.check_output(args)
        print out

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--toolchain", help="Path to the Xtensa toolchain",
                        default=os.path.join(os.environ.get("HOME"), ".platformio/packages/toolchain-xtensa32"))
    parser.add_argument("--elf", help="Path to the ELF file of the firmware",
                        default=".pioenvs/kbox32/firmware.elf")
    parser.add_argument("input", type=argparse.FileType('r'), default=sys.stdin)

    args = parser.parse_args()

    crash_parser = ESP32CrashParser(args.toolchain, args.elf)
    crash_parser.parse_text(args.input.read())


if __name__ == '__main__':
    main()