import serial
import base64
import cbor2
import struct
import time
import argparse
import sys

# Icons (NerdFont / Emoji)
ICON_DIR = "📁" 
ICON_FILE = "📄"

class nRF_FS_Client:
    def __init__(self, port, baud):
        try:
            self.ser = serial.Serial(port, baud, timeout=0.2)
            self.seq = 0
            self.ser.reset_input_buffer()
        except serial.SerialException as e:
            print(f"Fehler: Konnte {port} nicht öffnen ({e})")
            sys.exit(1)

    def crc16(self, data):
        crc = 0x0000
        for byte in data:
            crc ^= (byte << 8)
            for _ in range(8):
                if crc & 0x8000:
                    crc = (crc << 1) ^ 0x1021
                else:
                    crc = crc << 1
                crc &= 0xFFFF
        return crc

    def build_packet(self, group, cmd, payload):
        self.seq = (self.seq + 1) % 256
        cbor_payload = cbor2.dumps(payload)
        header = struct.pack(">BBHHBB", 0x00, 0x08, len(cbor_payload), group, self.seq, cmd)
        full_body = header + cbor_payload
        checksum = self.crc16(full_body)
        full_msg = full_body + struct.pack(">H", checksum)
        return struct.pack(">H", len(full_msg)) + full_msg

    def request(self, group, cmd, payload):
        packet = self.build_packet(group, cmd, payload)
        b64_data = base64.b64encode(packet).decode()
        self.ser.write(f"\x06\t{b64_data}\n".encode())
        
        full_response_b64 = ""
        expected_len = -1
        start_time = time.time()
        
        while (time.time() - start_time) < 3.0:
            line = self.ser.readline().strip()
            if not line:
                continue
            
            is_smp = line.startswith(b'\x06\t') or line.startswith(b'\x06\n')
            is_cont_special = line.startswith(b'\x04\x14') and expected_len > 0

            if is_smp or is_cont_special:
                full_response_b64 += line[2:].decode()
                try:
                    raw_data = base64.b64decode(full_response_b64)
                    if expected_len == -1 and len(raw_data) >= 2:
                        expected_len = struct.unpack(">H", raw_data[:2])[0]
                    
                    if expected_len != -1 and len(raw_data) >= expected_len + 2:
                        if raw_data[8] == self.seq:
                            return cbor2.loads(raw_data[10:-2])
                except:
                    continue
        return None

    def list_recursive(self, path="/", prefix=""):
        res = self.request(64, 0, {"path": path})
        if res is None or 'files' not in res:
            return

        # Sortierung: Verzeichnisse zuerst, dann Namen
        entries = sorted(res['files'], key=lambda x: (x.get('t', 'f') != 'd', x['n']))
        count = len(entries)

        for i, entry in enumerate(entries):
            is_last = (i == count - 1)
            name = entry['n']
            is_dir = entry.get('t', 'f').startswith('d')
            
            # Line-Art Auswahl
            # connector = "└── " if is_last else "├── "
            connector = "└─ " if is_last else "├─ "


            print(f"{prefix}{connector}{ICON_DIR if is_dir else ICON_FILE} {name}")
            
            if is_dir:
                # Prefix für die nächste Ebene erweitern
                extension = "   " if is_last else "│  "
                sub_path = f"{path}/{name}".replace("//", "/")
                self.list_recursive(sub_path, prefix + extension)

    def close(self):
        if hasattr(self, 'ser') and self.ser.is_open:
            self.ser.close()

def main():
    parser = argparse.ArgumentParser(description="nRF52840 LittleFS Tree Tool")
    parser.add_argument("port", help="Serieller Port (z.B. /dev/cu.usbmodem...)")
    args = parser.parse_args()

    client = nRF_FS_Client(args.port, 115200)
    print(f"--- Dateistruktur auf nRF ({args.port}) ---")
    try:
        # Initialer Aufruf
        client.list_recursive("/")
    finally:
        client.close()

if __name__ == "__main__":
    main()