#!/usr/bin/env python3
"""
MTK Flash Tool for macOS
========================
A fast MediaTek device flasher that works natively on macOS.
Reads SP Flash Tool scatter files and flashes firmware using
the MTK BROM/DA protocol with optimized bulk transfers.

Usage:
    # Flash all partitions from scatter file:
    python3 mtk_flash.py flash --scatter MT6789_scatter.txt --da DA_PL.bin

    # Flash specific partitions only:
    python3 mtk_flash.py flash --scatter MT6789_scatter.txt --da DA_PL.bin \
        --partitions boot,system,vendor

    # Just show scatter file info:
    python3 mtk_flash.py info --scatter MT6789_scatter.txt

    # Read back a partition:
    python3 mtk_flash.py read --da DA_PL.bin --addr 0x0 --length 0x100000 \
        --output readback.bin

Requirements:
    pip3 install pyusb
    brew install libusb
"""

import argparse
import sys
import os

# Add tool directory to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from scatter_parser import parse_scatter, auto_load_roms
from flasher import MTKFlasher, FlashError
from usb_connection import USBError
from brom import BromError
from da_protocol import DAError


def cmd_flash(args):
    """Flash firmware from scatter file."""
    if not os.path.isfile(args.scatter):
        print(f"Error: Scatter file not found: {args.scatter}")
        sys.exit(1)
    if not os.path.isfile(args.da):
        print(f"Error: DA file not found: {args.da}")
        sys.exit(1)

    partitions = None
    if args.partitions:
        partitions = [p.strip() for p in args.partitions.split(",")]

    flasher = MTKFlasher()
    flasher.flash_firmware(
        scatter_path=args.scatter,
        da_path=args.da,
        da_addr=args.da_addr,
        partitions=partitions,
        skip_format=args.skip_format,
        reboot=not args.no_reboot,
    )


def cmd_info(args):
    """Show scatter file information."""
    if not os.path.isfile(args.scatter):
        print(f"Error: Scatter file not found: {args.scatter}")
        sys.exit(1)

    scatter = parse_scatter(args.scatter)

    # Try to find ROM files
    rom_dir = args.rom_dir if args.rom_dir else os.path.dirname(args.scatter)
    auto_load_roms(scatter, rom_dir)

    print(scatter.summary())
    print()

    # Show download summary
    enabled = scatter.get_enabled_partitions()
    if enabled:
        total = sum(os.path.getsize(p.file_path) for p in enabled if p.file_path)
        print(f"Ready to flash: {len(enabled)} partitions, "
              f"{total / (1024*1024):.1f} MB total")
    else:
        print("No partitions with ROM files found.")
        print("Place ROM files in the same directory as the scatter file.")


def cmd_read(args):
    """Read back flash content."""
    if not os.path.isfile(args.da):
        print(f"Error: DA file not found: {args.da}")
        sys.exit(1)

    flasher = MTKFlasher()
    try:
        flasher.connect()
        flasher.upload_da(args.da, args.da_addr)

        print(f"[*] Reading {args.length:#x} bytes from {args.addr:#x}...")
        from flasher import ProgressBar

        progress = ProgressBar("Read", args.length)
        data = flasher.da.read_partition(
            args.addr, args.length,
            progress_cb=progress.update
        )

        with open(args.output, "wb") as f:
            f.write(data)
        print(f"[*] Saved to {args.output} ({len(data)} bytes)")

        flasher.finish(reboot=not args.no_reboot)

    except (FlashError, BromError, DAError, USBError) as e:
        print(f"\n[ERROR] {e}")
        flasher.finish(reboot=False)
        sys.exit(1)


def cmd_format(args):
    """Format (erase) a partition or range."""
    if not os.path.isfile(args.da):
        print(f"Error: DA file not found: {args.da}")
        sys.exit(1)

    flasher = MTKFlasher()
    try:
        flasher.connect()
        flasher.upload_da(args.da, args.da_addr)

        print(f"[*] Formatting {args.length:#x} bytes at {args.addr:#x}...")
        from flasher import ProgressBar

        progress = ProgressBar("Format", args.length)
        flasher.da.format_partition(
            args.addr, args.length,
            progress_cb=progress.update
        )
        print("[*] Format complete")

        flasher.finish(reboot=not args.no_reboot)

    except (FlashError, BromError, DAError, USBError) as e:
        print(f"\n[ERROR] {e}")
        flasher.finish(reboot=False)
        sys.exit(1)


def cmd_detect(args):
    """Detect connected MTK device."""
    flasher = MTKFlasher()
    try:
        info = flasher.connect(timeout_sec=args.timeout)
        print(f"\nDevice info:\n{info}")
        flasher.usb.disconnect()
    except (FlashError, BromError, USBError) as e:
        print(f"\n[ERROR] {e}")
        sys.exit(1)


def main():
    parser = argparse.ArgumentParser(
        prog="mtk_flash",
        description="MTK Flash Tool for macOS - Fast MediaTek device flasher",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  %(prog)s flash --scatter MT6789_scatter.txt --da DA_PL.bin
  %(prog)s flash --scatter scatter.txt --da DA_PL.bin --partitions boot,system
  %(prog)s info --scatter MT6789_scatter.txt
  %(prog)s detect
  %(prog)s read --da DA_PL.bin --addr 0x0 --length 0x100000 -o readback.bin
        """,
    )
    subparsers = parser.add_subparsers(dest="command", help="Command")

    # flash command
    p_flash = subparsers.add_parser("flash", help="Flash firmware from scatter file")
    p_flash.add_argument("--scatter", "-s", required=True, help="Scatter file path")
    p_flash.add_argument("--da", "-d", required=True, help="Download Agent (DA) file")
    p_flash.add_argument("--da-addr", type=lambda x: int(x, 0), default=0x200000,
                         help="DA load address (default: 0x200000)")
    p_flash.add_argument("--partitions", "-p",
                         help="Comma-separated partition names to flash (default: all)")
    p_flash.add_argument("--skip-format", action="store_true",
                         help="Skip format/erase before writing")
    p_flash.add_argument("--no-reboot", action="store_true",
                         help="Don't reboot after flashing")
    p_flash.set_defaults(func=cmd_flash)

    # info command
    p_info = subparsers.add_parser("info", help="Show scatter file information")
    p_info.add_argument("--scatter", "-s", required=True, help="Scatter file path")
    p_info.add_argument("--rom-dir", help="Directory with ROM files")
    p_info.set_defaults(func=cmd_info)

    # read command
    p_read = subparsers.add_parser("read", help="Read back flash content")
    p_read.add_argument("--da", "-d", required=True, help="DA file")
    p_read.add_argument("--da-addr", type=lambda x: int(x, 0), default=0x200000)
    p_read.add_argument("--addr", "-a", type=lambda x: int(x, 0), required=True,
                        help="Start address")
    p_read.add_argument("--length", "-l", type=lambda x: int(x, 0), required=True,
                        help="Read length")
    p_read.add_argument("--output", "-o", required=True, help="Output file")
    p_read.add_argument("--no-reboot", action="store_true")
    p_read.set_defaults(func=cmd_read)

    # format command
    p_fmt = subparsers.add_parser("format", help="Format (erase) flash region")
    p_fmt.add_argument("--da", "-d", required=True, help="DA file")
    p_fmt.add_argument("--da-addr", type=lambda x: int(x, 0), default=0x200000)
    p_fmt.add_argument("--addr", "-a", type=lambda x: int(x, 0), required=True,
                        help="Start address")
    p_fmt.add_argument("--length", "-l", type=lambda x: int(x, 0), required=True,
                        help="Format length")
    p_fmt.add_argument("--no-reboot", action="store_true")
    p_fmt.set_defaults(func=cmd_format)

    # detect command
    p_det = subparsers.add_parser("detect", help="Detect connected MTK device")
    p_det.add_argument("--timeout", type=int, default=60, help="Timeout in seconds")
    p_det.set_defaults(func=cmd_detect)

    args = parser.parse_args()

    if not args.command:
        parser.print_help()
        sys.exit(1)

    args.func(args)


if __name__ == "__main__":
    main()
