#!/usr/bin/env python3
#
# Copyright (c) 2020 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0

import argparse
import logging
import os
import socket
import sys

from coredump_parser.log_parser import CoredumpLogFile
from coredump_parser.elf_parser import CoredumpElfFile

import gdbstubs

LOGGING_FORMAT = "[%(levelname)s][%(name)s] %(message)s"

# Only bind to local host
GDBSERVER_HOST = ""


class FakeSocket:
    def __init__(self) -> None:
        self.in_stream = sys.stdin.buffer
        self.out_stream = sys.stdout.buffer

    def recv(self, bufsize):
        return self.in_stream.read(bufsize)

    def send(self, data):
        n = self.out_stream.write(data)
        self.out_stream.flush()
        return n

    def close(self):
        pass


def parse_args():
    parser = argparse.ArgumentParser(allow_abbrev=False)

    parser.add_argument("elffile", help="Zephyr ELF binary")
    parser.add_argument("logfile", help="Coredump binary log file")
    parser.add_argument("--debug", action="store_true",
                        help="Print extra debugging information")
    parser.add_argument("--port", type=int, default=1234,
                        help="GDB server port")
    parser.add_argument("--pipe", action="store_true",
                        help="Use stdio to communicate with gdb")
    parser.add_argument("-v", "--verbose", action="store_true",
                        help="Print more information")

    return parser.parse_args()


def main():
    args = parse_args()

    # Setup logging
    logging.basicConfig(format=LOGGING_FORMAT)

    # Setup logging for "parser"
    logger = logging.getLogger("parser")
    if args.debug:
        logger.setLevel(logging.DEBUG)
    elif args.verbose:
        logger.setLevel(logging.INFO)
    else:
        logger.setLevel(logging.WARNING)

    # Setup logging for follow code
    logger = logging.getLogger("gdbserver")
    if args.debug:
        logger.setLevel(logging.DEBUG)
    else:
        # Use INFO as default since we need to let user
        # know what is going on
        logger.setLevel(logging.INFO)

    # Setup logging for "gdbstub"
    logger = logging.getLogger("gdbstub")
    if args.debug:
        logger.setLevel(logging.DEBUG)
    elif args.verbose:
        logger.setLevel(logging.INFO)
    else:
        logger.setLevel(logging.WARNING)

    if not os.path.isfile(args.elffile):
        logger.error(f"Cannot find file {args.elffile}, exiting...")
        sys.exit(1)

    if not os.path.isfile(args.logfile):
        logger.error(f"Cannot find file {args.logfile}, exiting...")
        sys.exit(1)

    logger.info(f"Log file: {args.logfile}")
    logger.info(f"ELF file: {args.elffile}")

    # Parse the coredump binary log file
    logf = CoredumpLogFile(args.logfile)
    logf.open()
    if not logf.parse():
        logger.error("Cannot parse log file, exiting...")
        logf.close()
        sys.exit(1)

    # Parse ELF file for code and read-only data
    elff = CoredumpElfFile(args.elffile)
    elff.open()
    if not elff.parse():
        logger.error("Cannot parse ELF file, exiting...")
        elff.close()
        logf.close()
        sys.exit(1)

    gdbstub = gdbstubs.get_gdbstub(logf, elff)

    if not args.pipe:
        # Start a GDB server
        gdbserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        # Reuse address so we don't have to wait for socket to be
        # close before we can bind to the port again
        gdbserver.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        gdbserver.bind((GDBSERVER_HOST, args.port))
        gdbserver.listen(1)

        logger.info(f"Waiting GDB connection on port {args.port}...")

        conn, remote = gdbserver.accept()
    else:
        conn = FakeSocket()
        remote = "pipe"

    if conn:
        logger.info(f"Accepted GDB connection from {remote}")

        gdbstub.run(conn)

        conn.close()

    gdbserver.close()

    logger.info("GDB session finished.")

    elff.close()
    logf.close()


if __name__ == "__main__":
    main()
