"""
Finds and prints the crash report associated with a specific (binary filename, process id).
Waits (max_wait_time/attempts_remaining) between retries. 
By default, max_wait_time=5 and retry_count=10, which results in a total wait time of ~15s
Errors if the report cannot be found after `retry_count` retries.
"""
import sys, os, argparse, re, glob, shutil, time

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--pid', type=str, required=True, help='The process id of the process that crashed')
    parser.add_argument('--binary-filename', type=str, required=True, help='The name of the file that crashed')
    parser.add_argument('--retry-count', type=int, nargs='?', default=10, help='The number of retries to make')
    parser.add_argument('--max-wait-time', type=float, nargs='?', default=5.0, help='The max amount of seconds to wait between tries')

    parser.add_argument('--dir', nargs='?', type=str, default="~/Library/Logs/DiagnosticReports", help='The directory to look for the crash report')
    parser.add_argument('--outfile', nargs='?', type=argparse.FileType('r'), default=sys.stdout, help='Where to write the result')
    args = parser.parse_args()

    assert args.pid, "pid can't be empty"
    assert args.binary_filename, "binary-filename can't be empty"

    os.chdir(os.path.expanduser(args.dir))
    output_report_with_retries(args.outfile, args.pid.strip(), args.binary_filename, args.retry_count, args.max_wait_time)

def output_report_with_retries(outfile, pid, filename, attempts_remaining, max_wait_time):
    report_name = find_report_in_cur_dir(pid, filename)
    if report_name:
        with open(report_name, "r") as f:
            shutil.copyfileobj(f, outfile)
        return
    elif(attempts_remaining > 0):
        # As the number of attempts remaining decreases, increase the number of seconds waited
        # if the max wait time is 2s and there are 10 attempts remaining, wait .2 seconds.
        # if the max wait time is 2s and there are 2 attempts remaining, wait 1 second. 
        time.sleep(max_wait_time / attempts_remaining)
        output_report_with_retries(outfile, pid, filename, attempts_remaining - 1, max_wait_time)
    else:
        raise RuntimeError("Report not found for ({}, {}).".format(filename, pid))

def find_report_in_cur_dir(pid, filename):
    for report_name in sorted(glob.glob("{}_*.crash".format(filename)), reverse=True):
        # parse out pid from first line of report
        # `Process:               filename [pid]``
        with open(report_name) as cur_report:
            pattern = re.compile(r'Process: *{} \[([0-9]*)\]'.format(filename))
            cur_report_pid = pattern.search(cur_report.readline()).group(1)

        assert cur_report_pid and cur_report_pid.isdigit()
        if cur_report_pid == pid:
            return report_name

    # did not find the crash report
    return None
        

if __name__ == '__main__':
    main()
