import os
import sys
import subprocess
from subprocess import Popen, PIPE
import time
import glob
from multiprocessing import Pool

SUCCEEDED = "\033[32msucceeded\033[0m"
FAILED = "\033[31mfailed\033[0m"
SKIPPED = "\033[35mskipped\033[0m"
WARNING = "\033[33mwarnings\033[0m "

build_format = '| {:35} | {:9} | {:6} |'
build_separator = '-' * 59

# ci-arduino naming, fqbn
all_boards = [
    ['metro_m0_tinyusb', 'adafruit:samd:adafruit_metro_m0:usbstack=tinyusb'],
    ['metro_m4_tinyusb', 'adafruit:samd:adafruit_metro_m4:speed=120,usbstack=tinyusb'],
    ['nrf52840', 'adafruit:nrf52:feather52840'],
    ['feather_rp2040_tinyusb', 'rp2040:rp2040:adafruit_feather:flash=8388608_0,freq=125,dbgport=Disabled,dbglvl=None,usbstack=tinyusb'],
    ['metroesp32s2', 'espressif:esp32:adafruit_metro_esp32s2:CDCOnBoot=cdc,MSCOnBoot=default,DFUOnBoot=default,UploadMode=cdc,PSRAM=enabled,PartitionScheme=tinyuf2'],
    #[' ', 'espressif:esp32:adafruit_feather_esp32s3:FlashMode=qio,LoopCore=1,EventsCore=1,USBMode=default,CDCOnBoot=cdc,MSCOnBoot=default,DFUOnBoot=default,UploadMode=cdc,PartitionScheme=tinyuf2'],
]

# return [succeeded, failed, skipped]
def build_sketch(variant, sketch):
    ret = [0, 0, 0]

    name = variant[0]
    fqbn = variant[1]

    start_time = time.monotonic()
    # Skip if contains: ".board.test.skip" or ".all.test.skip"
    # Skip if not contains: ".board.test.only" for a specific board
    sketchdir = os.path.dirname(sketch)
    if os.path.exists(sketchdir + '/.all.test.skip') or os.path.exists(sketchdir + '/.' + name + '.test.skip') or \
            (glob.glob(sketchdir + "/.*.test.only") and not os.path.exists(sketchdir + '/.' + name + '.test.only')):
        success = SKIPPED
        ret[2] = 1
    else:
        build_result = subprocess.run("arduino-cli compile --warnings all --fqbn {} {}".format(fqbn, sketch),
                                      shell=True, stdout=PIPE, stderr=PIPE)

        # get stderr into a form where warning/error was output to stderr
        if build_result.returncode != 0:
            success = FAILED
            ret[1] = 1
        else:
            ret[0] = 1
            if build_result.stderr:
                success = WARNING
            else:
                success = SUCCEEDED

    build_duration = time.monotonic() - start_time
    print(build_format.format(os.path.basename(sketch), success, '{:5.2f}s'.format(build_duration)))

    if success != SKIPPED:
        # Build failed
        if build_result.returncode != 0:
            print(build_result.stdout.decode("utf-8"))

        # Build with warnings
        if build_result.stderr:
            print(build_result.stderr.decode("utf-8"))
    return ret


# return [succeeded, failed, skipped]
def build_variant(variant):
    print()
    print(build_separator)
    print('| {:^56} |'.format('Board ' + variant[0]))
    print(build_separator)
    print(build_format.format('Example', '\033[39mResult\033[0m', 'Time'))
    print(build_separator)

    with Pool(processes=os.cpu_count()) as pool:
        pool_args = list((map(lambda e, v=variant: [v, e], all_examples)))
        result = pool.starmap(build_sketch, pool_args)
        # sum all element of same index (column sum)
        return list(map(sum, list(zip(*result))))


if __name__ == '__main__':
    # build all variants if input not existed
    if len(sys.argv) > 1:
        build_boards = list(filter(lambda x: sys.argv[1] in x[0], all_boards))
    else:
        build_boards = all_boards

    all_examples = list(glob.iglob('examples/**/*.ino', recursive=True))
    all_examples.sort()

    total_time = time.monotonic()
    total_result = [0, 0, 0]
    for b in build_boards:
        ret = build_variant(b)
        total_result = list(map(lambda x, y: x + y, total_result, ret))

    # Build Summary
    total_time = time.monotonic() - total_time
    print(build_separator)
    print("Build Summary: {} {}, {} {}, {} {} and took {:.2f}s".format(total_result[0], SUCCEEDED, total_result[1],
                                                                       FAILED, total_result[2], SKIPPED, total_time))
    print(build_separator)
    sys.exit(total_result[1])
