#!/usr/bin/env python3
# Copyright(c) 2022 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
import os
import sys
import logging
import time
import argparse
import socket
import struct
import hashlib
from urllib.parse import urlparse

RET = 0
HOST = None
PORT = 0
PORT_LOG = 9999
PORT_REQ = PORT_LOG + 1
BUF_SIZE = 4096

# Define the command and its
# possible max size
CMD_LOG_START = "start_log"
CMD_DOWNLOAD = "download"
MAX_CMD_SZ = 16

# Define the header format and size for
# transmiting the firmware
PACKET_HEADER_FORMAT_FW = 'I 42s 32s'

logging.basicConfig()
log = logging.getLogger("cavs-client")
log.setLevel(logging.INFO)

class cavstool_client():
    def __init__(self, host, port, args):
        self.host = host
        self.port = port
        self.args = args
        self.sock = None
        self.cmd = None

    def send_cmd(self, cmd):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
            self.sock = sock
            self.cmd = cmd
            self.sock.connect((self.host, self.port))
            self.sock.sendall(cmd.encode("utf-8"))
            log.info(f"Sent:     {cmd}")
            ack = str(self.sock.recv(MAX_CMD_SZ), "utf-8")
            log.info(f"Receive: {ack}")

            if ack == CMD_LOG_START:
                self.monitor_log()
            elif ack == CMD_DOWNLOAD:
                self.run()
            else:
                log.error(f"Receive incorrect msg:{ack} expect:{cmd}")

    def uploading(self, filename):
        # Send the FW to server
        fname = os.path.basename(filename)
        fsize = os.path.getsize(filename)

        md5_tx = hashlib.md5(open(filename,'rb').read()).hexdigest()

        # Pack the header and the expecting packed size is 78 bytes.
        # The header by convention includes:
        # size(4), filename(42), MD5(32)
        values = (fsize, fname.encode('utf-8'), md5_tx.encode('utf-8'))
        log.info(f'filename:{fname}, size:{fsize}, md5:{md5_tx}')

        s = struct.Struct(PACKET_HEADER_FORMAT_FW)
        header_data = s.pack(*values)
        header_size = s.size
        log.info(f'header size: {header_size}')

        with open(filename,'rb') as f:
            log.info(f'Sending...')

            total = self.sock.send(header_data)
            total += self.sock.sendfile(f)

            log.info(f"Done Sending ({total}).")

            rck = self.sock.recv(MAX_CMD_SZ).decode("utf-8")
            log.info(f"RCK ({rck}).")
            if not rck == "success":
                global RET
                RET = -1
                log.error(f"Firmware uploading failed")

    def run(self):
        filename = str(self.args.fw_file)
        self.uploading(filename)

    def monitor_log(self):
        log.info(f"Start to monitor log output...")
        while True:
            # Receive data from the server and print out
            receive_log = str(self.sock.recv(BUF_SIZE), "utf-8").replace('\x00','')
            if receive_log:
                sys.stdout.write(f"{receive_log}")
                sys.stdout.flush()
                time.sleep(0.1)

    def __del__(self):
        self.sock.close()


def main():
    if args.log_only:
        log.info("Monitor process")

        try:
            client = cavstool_client(HOST, PORT, args)
            client.send_cmd(CMD_LOG_START)
        except KeyboardInterrupt:
            pass

    else:
        log.info("Uploading process")
        client = cavstool_client(HOST, PORT, args)
        client.send_cmd(CMD_DOWNLOAD)

ap = argparse.ArgumentParser(description="DSP loader/logger client tool", allow_abbrev=False)
ap.add_argument("-q", "--quiet", action="store_true",
                help="No loader output, just DSP logging")
ap.add_argument("-l", "--log-only", action="store_true",
                help="Don't load firmware, just show log output")
ap.add_argument("-s", "--server-addr", default="localhost",
                help="Specify the adsp server address")
ap.add_argument("-p", "--log-port", type=int,
                help="Specify the PORT that connected to log server")
ap.add_argument("-r", "--req-port", type=int,
                help="Specify the PORT that connected to request server")
ap.add_argument("fw_file", nargs="?", help="Firmware file")
args = ap.parse_args()

if args.quiet:
    log.setLevel(logging.WARN)

if args.log_port:
    PORT_LOG = args.log_port

if args.req_port:
    PORT_REQ = args.req_port

if args.server_addr:
    url = urlparse("//" + args.server_addr)

    if url.hostname:
        HOST = url.hostname

    if url.port:
        PORT = int(url.port)
    else:
        if args.log_only:
            PORT = PORT_LOG
        else:
            PORT = PORT_REQ

log.info(f"REMOTE HOST: {HOST} PORT: {PORT}")

if __name__ == "__main__":
    main()

    sys.exit(RET)
